ai-dev-browser 0.58.0__tar.gz → 0.58.2__tar.gz
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.
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.github/workflows/ci.yml +25 -3
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/PKG-INFO +11 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/README.md +10 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_extension_lifecycle.py +10 -2
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_session.py +35 -21
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_tab.py +70 -16
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/ax.py +19 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/browser.py +29 -2
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/chrome.py +10 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/elements.py +3 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/errors.py +12 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/ext_bridge.py +1 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/extension.py +29 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/mouse.py +9 -3
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/window.py +54 -9
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/background.js +32 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/manifest.json +1 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/page.py +102 -3
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/page_view.js +26 -3
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/session.py +10 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/window.py +35 -4
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/worker.py +14 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser.egg-info/PKG-INFO +11 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser.egg-info/SOURCES.txt +9 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser.egg-info/scm_file_list.json +9 -0
- ai_dev_browser-0.58.2/docs/BR-real-profile-hidden-input-resolution.md +83 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/docs/remote-views.md +27 -2
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/pyproject.toml +5 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/conftest.py +16 -3
- ai_dev_browser-0.58.2/tests/integration/extension_window_support.py +312 -0
- ai_dev_browser-0.58.2/tests/integration/native_window_occluder.py +99 -0
- ai_dev_browser-0.58.2/tests/integration/scenarios_extension_window.json +23 -0
- ai_dev_browser-0.58.2/tests/integration/scenarios_extension_window_previous.json +22 -0
- ai_dev_browser-0.58.2/tests/integration/test_extension_window_native.py +77 -0
- ai_dev_browser-0.58.2/tests/integration/test_extension_window_previous.py +67 -0
- ai_dev_browser-0.58.2/tests/integration/test_remote_selection_live.py +368 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_remote_view.py +7 -1
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/extension_ownership.test.cjs +31 -1
- ai_dev_browser-0.58.2/tests/unit/test_window_input_contract.py +119 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.dockerignore +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.gitattributes +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.github/workflows/publish.yml +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.gitignore +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.gitmodules +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.pre-commit-config.yaml +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/.secrets.baseline +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/LICENSE +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/_cli.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/_version.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/accessibility.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/animation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/audits.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/autofill.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/background_service.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/bluetooth_emulation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/browser.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/cache_storage.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/cast.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/console.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/crash_report_context.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/css.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/debugger.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/device_access.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/device_orientation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/dom.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/dom_debugger.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/dom_snapshot.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/dom_storage.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/emulation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/event_breakpoints.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/extensions.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/fed_cm.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/fetch.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/file_system.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/headless_experimental.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/heap_profiler.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/indexed_db.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/input_.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/inspector.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/io.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/layer_tree.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/log.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/media.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/memory.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/network.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/overlay.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/page.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/performance.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/performance_timeline.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/preload.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/profiler.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/pwa.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/py.typed +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/runtime.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/schema.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/security.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/service_worker.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/smart_card_emulation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/storage.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/system_info.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/target.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/tethering.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/tracing.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/util.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/web_audio.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/web_authn.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/cdp/web_mcp.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_browser_guidance.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_case.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_demo.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_element.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_image_cap.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_js.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_recorder.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/_transport.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/cdp.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/cleanup.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/config.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/connection.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/cookies.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/cookies_import.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/dialog.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/download.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/human.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/identity.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/login.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/navigation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/overlays.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/page.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/port.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/process.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/recording.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/registry.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/snapshot.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/storage.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/tabs.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/core/text_match.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/README.md +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/icon128.png +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/icon16.png +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/icon48.png +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/extension/managed-schema.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/pool/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/pool/job.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/pool/persistence.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/pool/pool.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/pool/worker.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/profile.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/py.typed +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/cdp.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/control.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/existing.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/files.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/mcp-tools.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/profile_lease.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/provision.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/remote/vnc.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/_generate.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_cleanup.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_connect.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_disconnect.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_list.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_reconnect.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_restart.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_start.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/browser_stop.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/cdp_send.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/click_by_html_id.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/click_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/click_by_text.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/click_by_xpath.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/click_row_by_text.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/cookies_extract_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/cookies_extract_offline.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/cookies_import.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/cookies_load.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/cookies_save.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/dialog_respond.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/download.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/download_link.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/drag_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/find_by_html_id.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/find_by_text.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/find_by_xpath.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/focus_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/highlight_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/hover_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/html_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/js_evaluate.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/login_interactive.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/mouse_click.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/mouse_drag.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/mouse_move.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_discover.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_emulate_focus.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_goto.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_html.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_info.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_pdf.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_record_start.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_record_stop.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_reload.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_screenshot.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_scroll.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_wait_element.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_wait_ready.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/page_wait_url.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/press_key.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/screenshot_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/select_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/select_text.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/storage_get.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/storage_set.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/tab_close.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/tab_list.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/tab_new.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/tab_switch.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/type_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/type_by_text.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/upload_by_ref.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser/tools/window_set.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser.egg-info/dependency_links.txt +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser.egg-info/requires.txt +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/ai_dev_browser.egg-info/top_level.txt +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/deploy/managed-chrome/Dockerfile +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/deploy/managed-chrome/configure_extension.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/docs/navigation-ownership.md +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/scripts/build_release_assets.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/scripts/live_browser_lifecycle_steering.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/scripts/live_grok_pool_persistence.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/scripts/live_recording_steering.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/scripts/sync_cdp.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/setup.cfg +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/conftest.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/__init__.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/browser_lifecycle_acceptance.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/browser_lifecycle_journeys.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/browser_lifecycle_support.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/browser_lifecycle_ui.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/cli_steering_acceptance.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/cli_steering_support.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/extension_ownership_support.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/live_cleanup.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/navigation_startup_support.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/pdf_readiness_support.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/pool-persistence-acceptance.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/recording_demo_acceptance.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/recording_demo_support.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/run_managed_extension.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_browser_lifecycle.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_cli_extension.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_cli_steering.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_extension_ownership.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_extension_ownership_native.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_navigation_startup.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_pdf_readiness.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_public_web.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_recording.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_recording_demo.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/scenarios_workspace.json +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_background_processes.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_control_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_files_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_inventory.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_lifecycle.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_session_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_start_retry.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_browser_worker_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cdp_send_warning.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_ci_integration.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cli_extension_workflows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cli_steering.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cli_unicode_help.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_click_robust.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_config_resolvers.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cookies_extract_hermetic.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cookies_extract_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cookies_extract_windows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_cookies_import.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_dialog_auto.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_dialog_respond.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_dom_discovery.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_download_link.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_download_link_frame.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_existing_worker_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_ext_bridge.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_extension_ownership.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_extension_ownership_native.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_find_and_interact_workflows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_image_cap.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_js_contract.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_js_evaluate_frame.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_locator_workflows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_mouse_click.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_mouse_drag_move.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_multi_profile_pool.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_navigation_startup.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_page_discover.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_page_goto.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_page_pdf.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_page_reload.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_page_scroll_verify.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_page_wait_element.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_pdf_readiness.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_pool_model_persistence.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_port_detection.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_press_key.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_proxy_identity.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_public_web_workflows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_recording_demo.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_recording_workflows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_remote_vnc.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_remote_window_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_screenshot_coords.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_scroll_embedded_target.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_select_text.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_startup_timeout.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_stealth_defaults.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_tab_close.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_tab_url_matching.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_text_locator.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_timeout_and_retry.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_trusted_click.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_type_verified_fill.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_viewport.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_worker_managed_status_live.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/integration/test_workspace_workflows.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_browser_control.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_build_info.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_case.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cdp_coerce.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cdp_session_scoped.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cdp_unbound.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cli_docstring_args.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cli_error_envelope.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cli_param_type.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_cli_steering_contract.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_demo_encoding.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_extension_ownership.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_find_hint.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_launch_window_size.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_lifecycle_handshake_errors.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_live_cleanup.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_no_acronym_rot.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_pdf_units.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_recording.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_release_version.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_remote_files.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_remote_navigation.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_session_failure_state.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_tool_registry.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/tests/unit/test_worker_lifecycle_status.py +0 -0
- {ai_dev_browser-0.58.0 → ai_dev_browser-0.58.2}/uv.lock +0 -0
|
@@ -31,6 +31,9 @@ jobs:
|
|
|
31
31
|
- name: Install package
|
|
32
32
|
run: uv sync --extra dev --extra remote
|
|
33
33
|
|
|
34
|
+
- name: Check extension window and ownership contracts
|
|
35
|
+
run: node --test tests/unit/extension_ownership.test.cjs
|
|
36
|
+
|
|
34
37
|
# ── Tool dispatch smoke test ──
|
|
35
38
|
# Verifies python -m entry point works on all platforms.
|
|
36
39
|
- name: Test tool dispatch
|
|
@@ -158,19 +161,19 @@ jobs:
|
|
|
158
161
|
- run: python -m pip install ".[dev,remote]"
|
|
159
162
|
- name: Install the real X11 and noVNC viewer
|
|
160
163
|
if: runner.os == 'Linux'
|
|
161
|
-
run: sudo apt-get update -qq && sudo apt-get install -y xvfb xauth x11vnc novnc websockify
|
|
164
|
+
run: sudo apt-get update -qq && sudo apt-get install -y xvfb xauth x11vnc novnc websockify openbox xdotool xclip
|
|
162
165
|
- name: Run page, extension and full-window workflows on Linux
|
|
163
166
|
if: runner.os == 'Linux'
|
|
164
167
|
env:
|
|
165
168
|
AI_DEV_BROWSER_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
166
169
|
AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
167
|
-
run: xvfb-run -a -s "-screen 0 1280x1000x24" python -m pytest -v --full-trace -o faulthandler_timeout=180 tests/integration/test_remote_view.py tests/integration/test_remote_vnc.py tests/integration/test_browser_control_live.py tests/integration/test_browser_session_live.py tests/integration/test_browser_files_live.py tests/integration/test_browser_worker_live.py tests/integration/test_existing_worker_live.py tests/integration/test_navigation_startup.py --basetemp remote-results
|
|
170
|
+
run: xvfb-run -a -s "-screen 0 1280x1000x24" python -m pytest -v --full-trace -o faulthandler_timeout=180 tests/integration/test_remote_view.py tests/integration/test_remote_selection_live.py tests/integration/test_remote_vnc.py tests/integration/test_browser_control_live.py tests/integration/test_browser_session_live.py tests/integration/test_browser_files_live.py tests/integration/test_browser_worker_live.py tests/integration/test_existing_worker_live.py tests/integration/test_navigation_startup.py --basetemp remote-results
|
|
168
171
|
- name: Run page and extension workflows on Windows
|
|
169
172
|
if: runner.os == 'Windows'
|
|
170
173
|
env:
|
|
171
174
|
AI_DEV_BROWSER_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
172
175
|
AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
173
|
-
run: python -m pytest -v --full-trace -o faulthandler_timeout=180 tests/integration/test_remote_view.py tests/integration/test_browser_control_live.py tests/integration/test_browser_session_live.py tests/integration/test_browser_files_live.py tests/integration/test_browser_worker_live.py tests/integration/test_existing_worker_live.py tests/integration/test_navigation_startup.py --basetemp remote-results
|
|
176
|
+
run: python -m pytest -v --full-trace -o faulthandler_timeout=180 tests/integration/test_remote_view.py tests/integration/test_remote_selection_live.py tests/integration/test_browser_control_live.py tests/integration/test_browser_session_live.py tests/integration/test_browser_files_live.py tests/integration/test_browser_worker_live.py tests/integration/test_existing_worker_live.py tests/integration/test_navigation_startup.py --basetemp remote-results
|
|
174
177
|
- name: Retain real viewer evidence
|
|
175
178
|
if: always()
|
|
176
179
|
uses: actions/upload-artifact@v4
|
|
@@ -249,6 +252,21 @@ jobs:
|
|
|
249
252
|
AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
250
253
|
run: uv run --no-sync python -m pytest -v --full-trace -o faulthandler_timeout=180 tests/integration/test_cli_extension_workflows.py tests/integration/test_recording_workflows.py tests/integration/test_recording_demo.py tests/integration/test_browser_lifecycle.py tests/integration/test_remote_view.py tests/integration/test_extension_ownership.py tests/integration/test_cli_steering.py -k "real_extension or outbound_proxy" --basetemp extension-results
|
|
251
254
|
|
|
255
|
+
- name: Install the unchanged released extension fixture
|
|
256
|
+
run: uv pip install --target previous-extension-fixture --no-deps ai-dev-browser==0.57.7
|
|
257
|
+
|
|
258
|
+
- name: Verify live upgrade diagnostics against the released extension
|
|
259
|
+
env:
|
|
260
|
+
AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
261
|
+
AI_DEV_BROWSER_TEST_PREVIOUS_EXTENSION: ${{ github.workspace }}/previous-extension-fixture/ai_dev_browser/extension
|
|
262
|
+
run: uv run --no-sync python -m pytest -v tests/integration/test_extension_window_previous.py --basetemp previous-extension-results
|
|
263
|
+
|
|
264
|
+
- name: Honor genuine Windows window visibility before input
|
|
265
|
+
if: runner.os == 'Windows'
|
|
266
|
+
env:
|
|
267
|
+
AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
|
|
268
|
+
run: uv run --no-sync python -m pytest -v tests/integration/test_extension_window_native.py --basetemp native-window-results
|
|
269
|
+
|
|
252
270
|
- name: Install native Chrome interaction tools
|
|
253
271
|
if: runner.os == 'Linux'
|
|
254
272
|
run: sudo apt-get update -qq && sudo apt-get install -y xvfb xauth openbox xdotool
|
|
@@ -285,6 +303,10 @@ jobs:
|
|
|
285
303
|
extension-results/**/extension-chrome.log
|
|
286
304
|
extension-results/**/extension-cleanup-stacks.txt
|
|
287
305
|
native-ownership-results/**
|
|
306
|
+
native-window-results/**/native-window-evidence.json
|
|
307
|
+
native-window-results/**/native-window-accepted.png
|
|
308
|
+
native-window-results/**/extension-chrome.log
|
|
309
|
+
previous-extension-results/**/previous-extension-evidence.json
|
|
288
310
|
|
|
289
311
|
# ─────────────────────────────────────────────────────────────────────
|
|
290
312
|
# CI-integration dogfood: prove the documented "consumer's CI" recipe
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-dev-browser
|
|
3
|
-
Version: 0.58.
|
|
3
|
+
Version: 0.58.2
|
|
4
4
|
Summary: A browser for AI to develop web automation — human-like automation that works seamlessly in a world designed for humans
|
|
5
5
|
Author: sudoprivacy
|
|
6
6
|
License: AGPL-3.0
|
|
@@ -454,6 +454,16 @@ Each GitHub release includes the same wheel and source distribution sent to PyPI
|
|
|
454
454
|
the bundled Chrome extension as an unpackable ZIP, and `SHA256SUMS`.
|
|
455
455
|
For extension mode, extract the ZIP and load its directory in `chrome://extensions`.
|
|
456
456
|
After upgrading, reload the extension and restart an already running bridge.
|
|
457
|
+
`browser_connect` reports the actual loaded `extension_version`, the
|
|
458
|
+
`bundled_extension_version`, and whether they match. Updating Python alone does
|
|
459
|
+
not reload Chrome's extension. Reload can reset control; inspect the session and
|
|
460
|
+
its owned page before resuming input.
|
|
461
|
+
|
|
462
|
+
For a hidden extension page, `window_set --state normal --focus` requests real
|
|
463
|
+
window restoration and reports observed `visibility`, `focused`, and
|
|
464
|
+
`input_ready`. A focused window may still be occluded. Keep it genuinely visible
|
|
465
|
+
before input; focus emulation does not resolve a hidden-page refusal. See the
|
|
466
|
+
[live window investigation](docs/BR-real-profile-hidden-input-resolution.md).
|
|
457
467
|
|
|
458
468
|
Maintainers: run live browser acceptance and wait for CI on the release commit.
|
|
459
469
|
Create an annotated `vX.Y.Z` tag with `git tag -a --cleanup=verbatim` and use the
|
|
@@ -412,6 +412,16 @@ Each GitHub release includes the same wheel and source distribution sent to PyPI
|
|
|
412
412
|
the bundled Chrome extension as an unpackable ZIP, and `SHA256SUMS`.
|
|
413
413
|
For extension mode, extract the ZIP and load its directory in `chrome://extensions`.
|
|
414
414
|
After upgrading, reload the extension and restart an already running bridge.
|
|
415
|
+
`browser_connect` reports the actual loaded `extension_version`, the
|
|
416
|
+
`bundled_extension_version`, and whether they match. Updating Python alone does
|
|
417
|
+
not reload Chrome's extension. Reload can reset control; inspect the session and
|
|
418
|
+
its owned page before resuming input.
|
|
419
|
+
|
|
420
|
+
For a hidden extension page, `window_set --state normal --focus` requests real
|
|
421
|
+
window restoration and reports observed `visibility`, `focused`, and
|
|
422
|
+
`input_ready`. A focused window may still be occluded. Keep it genuinely visible
|
|
423
|
+
before input; focus emulation does not resolve a hidden-page refusal. See the
|
|
424
|
+
[live window investigation](docs/BR-real-profile-hidden-input-resolution.md).
|
|
415
425
|
|
|
416
426
|
Maintainers: run live browser acceptance and wait for CI on the release commit.
|
|
417
427
|
Create an annotated `vX.Y.Z` tag with `git tag -a --cleanup=verbatim` and use the
|
|
@@ -66,6 +66,7 @@ async def start(
|
|
|
66
66
|
url,
|
|
67
67
|
startup_timeout,
|
|
68
68
|
extra_args,
|
|
69
|
+
display=None,
|
|
69
70
|
) -> dict:
|
|
70
71
|
if not user_data_dir:
|
|
71
72
|
return _session.failure(
|
|
@@ -105,7 +106,9 @@ async def start(
|
|
|
105
106
|
if (
|
|
106
107
|
not headless
|
|
107
108
|
and sys.platform.startswith("linux")
|
|
108
|
-
and not (
|
|
109
|
+
and not (
|
|
110
|
+
display or os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
|
|
111
|
+
)
|
|
109
112
|
):
|
|
110
113
|
return _session.failure(
|
|
111
114
|
"unsupported",
|
|
@@ -174,7 +177,11 @@ async def start(
|
|
|
174
177
|
command += arguments + [
|
|
175
178
|
f"http://127.0.0.1:{EXTENSION_BRIDGE_PORT}/adb-launch/{token}"
|
|
176
179
|
]
|
|
177
|
-
process = _spawn_chrome(
|
|
180
|
+
process = _spawn_chrome(
|
|
181
|
+
command,
|
|
182
|
+
silent_stderr=True,
|
|
183
|
+
**({"display": display} if display is not None else {}),
|
|
184
|
+
)
|
|
178
185
|
observed = _session.process_info(process.pid)
|
|
179
186
|
record = {
|
|
180
187
|
"transport": "extension",
|
|
@@ -193,6 +200,7 @@ async def start(
|
|
|
193
200
|
"headless": headless,
|
|
194
201
|
"startup_timeout": startup_timeout,
|
|
195
202
|
"extra_args": arguments,
|
|
203
|
+
"display": display,
|
|
196
204
|
},
|
|
197
205
|
}
|
|
198
206
|
if observed:
|
|
@@ -211,24 +211,38 @@ def capabilities(record: dict) -> dict:
|
|
|
211
211
|
|
|
212
212
|
|
|
213
213
|
def describe(record: dict) -> dict:
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
214
|
+
return (
|
|
215
|
+
{
|
|
216
|
+
key: record.get(key)
|
|
217
|
+
for key in (
|
|
218
|
+
"session_id",
|
|
219
|
+
"transport",
|
|
220
|
+
"owner",
|
|
221
|
+
"profile_owner",
|
|
222
|
+
"pid",
|
|
223
|
+
"port",
|
|
224
|
+
"chrome_path",
|
|
225
|
+
"user_data_dir",
|
|
226
|
+
"profile_directory",
|
|
227
|
+
"profile_id",
|
|
228
|
+
"headless",
|
|
229
|
+
"browser_version",
|
|
230
|
+
"automation_target_id",
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
| {
|
|
234
|
+
"launch_chrome_path": (record.get("start_options") or {}).get(
|
|
235
|
+
"chrome_path"
|
|
236
|
+
),
|
|
237
|
+
"capabilities": capabilities(record),
|
|
238
|
+
}
|
|
239
|
+
| {
|
|
240
|
+
key: record.get(key)
|
|
241
|
+
for key in (
|
|
242
|
+
"extension_version",
|
|
243
|
+
"bundled_extension_version",
|
|
244
|
+
"extension_version_matches_bundle",
|
|
245
|
+
)
|
|
246
|
+
if record.get("transport") == "extension"
|
|
247
|
+
}
|
|
248
|
+
)
|
|
@@ -918,7 +918,25 @@ class Tab:
|
|
|
918
918
|
async def _set_window_state(self, state: str):
|
|
919
919
|
window_id, _ = await self.get_window()
|
|
920
920
|
bounds = cdp_browser.Bounds(window_state=cdp_browser.WindowState(state))
|
|
921
|
-
|
|
921
|
+
try:
|
|
922
|
+
await self.send(cdp_browser.set_window_bounds(window_id, bounds))
|
|
923
|
+
except ProtocolException as error:
|
|
924
|
+
if getattr(
|
|
925
|
+
self._browser, "transport", "cdp"
|
|
926
|
+
) != "extension" or not _is_cdp_method_not_found(error):
|
|
927
|
+
raise
|
|
928
|
+
from .errors import InputUnavailableError
|
|
929
|
+
|
|
930
|
+
observed_state: dict = {"visibility": "unknown", "focused": None}
|
|
931
|
+
try:
|
|
932
|
+
observed_state = await self._input_state(timeout=1)
|
|
933
|
+
except Exception:
|
|
934
|
+
pass # A diagnostic read must not replace the unsupported-route error.
|
|
935
|
+
raise InputUnavailableError(
|
|
936
|
+
"The loaded extension does not support Browser.setWindowBounds; "
|
|
937
|
+
"the requested window state was not applied and no input was sent",
|
|
938
|
+
details=await self._input_diagnostics(observed_state),
|
|
939
|
+
) from error
|
|
922
940
|
|
|
923
941
|
async def maximize(self):
|
|
924
942
|
await self._set_window_state("maximized")
|
|
@@ -941,6 +959,45 @@ class Tab:
|
|
|
941
959
|
"""Alias for activate()."""
|
|
942
960
|
await self.activate()
|
|
943
961
|
|
|
962
|
+
async def _input_state(self, *, timeout: float | None = None) -> dict:
|
|
963
|
+
"""Read genuine page visibility and document focus independently."""
|
|
964
|
+
return await self.evaluate(
|
|
965
|
+
"({visibility:document.visibilityState,focused:document.hasFocus()})",
|
|
966
|
+
timeout=timeout,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
async def _wait_input_visibility(self) -> dict:
|
|
970
|
+
deadline = time.monotonic() + 2
|
|
971
|
+
state = await self._input_state()
|
|
972
|
+
while state["visibility"] != "visible" and time.monotonic() < deadline:
|
|
973
|
+
await asyncio.sleep(0.05)
|
|
974
|
+
state = await self._input_state()
|
|
975
|
+
return state
|
|
976
|
+
|
|
977
|
+
async def _input_diagnostics(self, state: dict) -> dict:
|
|
978
|
+
"""Bounded, read-only support data; never include flow URLs or accounts."""
|
|
979
|
+
from .extension import extension_version_info, query_extension_version
|
|
980
|
+
|
|
981
|
+
details = {
|
|
982
|
+
"input_sent": False,
|
|
983
|
+
"input_scope": "current_dispatch",
|
|
984
|
+
"target_id": str(self._target.target_id),
|
|
985
|
+
**state,
|
|
986
|
+
}
|
|
987
|
+
if getattr(self._browser, "transport", "cdp") != "extension":
|
|
988
|
+
return details
|
|
989
|
+
loaded = await query_extension_version(self._browser)
|
|
990
|
+
details.update(extension_version_info(loaded))
|
|
991
|
+
try:
|
|
992
|
+
window_id, bounds = await self.send(
|
|
993
|
+
cdp_browser.get_window_for_target(self._target.target_id), timeout=1
|
|
994
|
+
)
|
|
995
|
+
details["window_state"] = bounds.window_state.value
|
|
996
|
+
details["window_id"] = window_id
|
|
997
|
+
except Exception:
|
|
998
|
+
pass # Preserve the original refusal if diagnostic support is incomplete.
|
|
999
|
+
return details
|
|
1000
|
+
|
|
944
1001
|
async def _prepare_input(self, allow_cached: bool = False):
|
|
945
1002
|
"""Wake only the selected extension page before trusted input."""
|
|
946
1003
|
if getattr(self._browser, "transport", "cdp") != "extension":
|
|
@@ -950,26 +1007,23 @@ class Tab:
|
|
|
950
1007
|
and time.monotonic() - getattr(self, "_input_checked_at", 0) < 0.5
|
|
951
1008
|
):
|
|
952
1009
|
return
|
|
953
|
-
|
|
954
|
-
if
|
|
1010
|
+
state = await self._input_state()
|
|
1011
|
+
if state["visibility"] != "visible":
|
|
955
1012
|
await self.bring_to_front()
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if await self.evaluate("document.visibilityState === 'visible'"):
|
|
959
|
-
# Let the restored renderer paint before its first pointer event.
|
|
960
|
-
await self.evaluate(
|
|
961
|
-
"new Promise(resolve => requestAnimationFrame(() => resolve(true)))",
|
|
962
|
-
await_promise=True,
|
|
963
|
-
timeout=2,
|
|
964
|
-
)
|
|
965
|
-
break
|
|
966
|
-
await asyncio.sleep(0.05)
|
|
967
|
-
else:
|
|
1013
|
+
state = await self._wait_input_visibility()
|
|
1014
|
+
if state["visibility"] != "visible":
|
|
968
1015
|
from .errors import InputUnavailableError
|
|
969
1016
|
|
|
970
1017
|
raise InputUnavailableError(
|
|
971
|
-
"The automation page is still hidden after activation; this input was not sent. Show its Chrome window, then inspect page state before retrying"
|
|
1018
|
+
"The automation page is still hidden after activation; this input was not sent. Show its Chrome window, then inspect page state before retrying",
|
|
1019
|
+
details=await self._input_diagnostics(state),
|
|
972
1020
|
)
|
|
1021
|
+
# Let the restored renderer paint before its first pointer event.
|
|
1022
|
+
await self.evaluate(
|
|
1023
|
+
"new Promise(resolve => requestAnimationFrame(() => resolve(true)))",
|
|
1024
|
+
await_promise=True,
|
|
1025
|
+
timeout=2,
|
|
1026
|
+
)
|
|
973
1027
|
self._input_checked_at = time.monotonic()
|
|
974
1028
|
|
|
975
1029
|
async def close(self):
|
|
@@ -871,10 +871,14 @@ async def _type_keystrokes(tab: Tab, text: str) -> None:
|
|
|
871
871
|
async def _focus_el(element: Element) -> None:
|
|
872
872
|
try:
|
|
873
873
|
await element.focus() # CDP DOM.focus
|
|
874
|
+
except SessionError:
|
|
875
|
+
raise
|
|
874
876
|
except Exception:
|
|
875
877
|
pass
|
|
876
878
|
try:
|
|
877
879
|
await element.apply("(el) => { if (el && el.focus) el.focus(); }")
|
|
880
|
+
except SessionError:
|
|
881
|
+
raise
|
|
878
882
|
except Exception:
|
|
879
883
|
pass
|
|
880
884
|
|
|
@@ -883,10 +887,12 @@ async def _clear_el(tab: Tab, element: Element) -> None:
|
|
|
883
887
|
try:
|
|
884
888
|
await element.apply(
|
|
885
889
|
"(el) => { if (!el) return; if (el.focus) el.focus();"
|
|
886
|
-
" if ('value' in el) { el.value = '';"
|
|
890
|
+
" if ('value' in el && el.value !== '') { el.value = '';"
|
|
887
891
|
" el.dispatchEvent(new Event('input', {bubbles: true})); }"
|
|
888
892
|
" else if (el.isContentEditable) { el.textContent = ''; } }"
|
|
889
893
|
)
|
|
894
|
+
except SessionError:
|
|
895
|
+
raise
|
|
890
896
|
except Exception:
|
|
891
897
|
pass
|
|
892
898
|
|
|
@@ -908,6 +914,8 @@ async def _field_state(element: Element, text: str) -> dict:
|
|
|
908
914
|
if isinstance(st, dict)
|
|
909
915
|
else {"matches": False, "empty": True, "length": 0}
|
|
910
916
|
)
|
|
917
|
+
except SessionError:
|
|
918
|
+
raise
|
|
911
919
|
except Exception:
|
|
912
920
|
return {"matches": False, "empty": True, "length": 0}
|
|
913
921
|
|
|
@@ -948,6 +956,11 @@ async def _fill_verified(
|
|
|
948
956
|
tried: list[str] = []
|
|
949
957
|
st: dict | None = None
|
|
950
958
|
for i, method in enumerate(order):
|
|
959
|
+
prepare_input = getattr(tab, "_prepare_input", None)
|
|
960
|
+
if prepare_input:
|
|
961
|
+
# This precedes even clearing/focusing: a hidden-page or ownership
|
|
962
|
+
# refusal must not turn into a value setter on a later rung.
|
|
963
|
+
await prepare_input()
|
|
951
964
|
await _clear_el(tab, element)
|
|
952
965
|
await _focus_el(element)
|
|
953
966
|
tried.append(method)
|
|
@@ -960,6 +973,8 @@ async def _fill_verified(
|
|
|
960
973
|
await human.type_text(tab, text, element, humanize=True)
|
|
961
974
|
elif method == "native":
|
|
962
975
|
await _native_set(element, text)
|
|
976
|
+
except SessionError:
|
|
977
|
+
raise
|
|
963
978
|
except Exception:
|
|
964
979
|
continue
|
|
965
980
|
st = await _field_state(element, text)
|
|
@@ -994,6 +1009,9 @@ async def type_by_ref(
|
|
|
994
1009
|
(plus `entered` when `enter=True`) — `verified` already tells you the value
|
|
995
1010
|
actually landed, so don't screenshot or re-read the field to check.
|
|
996
1011
|
|
|
1012
|
+
In extension mode, hidden-page or ownership refusals stop the operation
|
|
1013
|
+
before clearing; input fallbacks never override these refusals.
|
|
1014
|
+
|
|
997
1015
|
If you can identify the input by its visible label / placeholder, skip
|
|
998
1016
|
`page_discover` and use `type_by_text` directly.
|
|
999
1017
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
import time
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
@@ -23,6 +24,8 @@ from .extension import (
|
|
|
23
24
|
EXTENSION_BRIDGE_PORT,
|
|
24
25
|
extension_dir,
|
|
25
26
|
extension_load_instructions,
|
|
27
|
+
extension_version_info,
|
|
28
|
+
query_extension_version,
|
|
26
29
|
)
|
|
27
30
|
from .port import (
|
|
28
31
|
_cleanup_temp_profile,
|
|
@@ -202,6 +205,7 @@ def browser_start(
|
|
|
202
205
|
chrome_path: str | None = None,
|
|
203
206
|
user_data_dir: str | None = None,
|
|
204
207
|
profile_directory: str = "Default",
|
|
208
|
+
display: str | None = None,
|
|
205
209
|
) -> dict:
|
|
206
210
|
"""Use when: you need a new isolated CDP session or explicitly launch a configured extension profile; returns session_id and actual capabilities.
|
|
207
211
|
|
|
@@ -236,6 +240,9 @@ def browser_start(
|
|
|
236
240
|
transport: "cdp" (isolated automation, default) or "extension"
|
|
237
241
|
(explicitly launch an already configured real Chrome/profile).
|
|
238
242
|
chrome_path: Chrome executable to launch; otherwise find_chrome resolves it.
|
|
243
|
+
display: Optional local X11 display ID, such as ":2", for a host-owned
|
|
244
|
+
GUI. Passed only to this Chrome child; the calling process keeps
|
|
245
|
+
its DISPLAY unchanged. Omit to use the existing desktop display.
|
|
239
246
|
user_data_dir: Existing Chrome user-data root, required for extension
|
|
240
247
|
startup. CDP uses temp/named adb profiles; this option is extension only.
|
|
241
248
|
profile_directory: Chrome folder inside user_data_dir, e.g. "Default"
|
|
@@ -341,6 +348,14 @@ def browser_start(
|
|
|
341
348
|
replay input implicitly after failure.
|
|
342
349
|
"""
|
|
343
350
|
start_options = dict(locals())
|
|
351
|
+
if display is not None and (
|
|
352
|
+
not isinstance(display, str) or not re.fullmatch(r":\d+(?:\.\d+)?", display)
|
|
353
|
+
):
|
|
354
|
+
return _session.failure(
|
|
355
|
+
"validation",
|
|
356
|
+
"An explicit display must be a local X11 display ID",
|
|
357
|
+
transport=transport,
|
|
358
|
+
)
|
|
344
359
|
startup_timeout = resolve_startup_timeout(startup_timeout)
|
|
345
360
|
if transport == "extension":
|
|
346
361
|
if (
|
|
@@ -369,6 +384,7 @@ def browser_start(
|
|
|
369
384
|
url=url,
|
|
370
385
|
startup_timeout=startup_timeout,
|
|
371
386
|
extra_args=extra_args,
|
|
387
|
+
display=display,
|
|
372
388
|
)
|
|
373
389
|
)
|
|
374
390
|
if (
|
|
@@ -386,7 +402,7 @@ def browser_start(
|
|
|
386
402
|
import sys
|
|
387
403
|
|
|
388
404
|
if sys.platform.startswith("linux") and not (
|
|
389
|
-
os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
|
|
405
|
+
display or os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
|
|
390
406
|
):
|
|
391
407
|
return _session.failure(
|
|
392
408
|
"unsupported",
|
|
@@ -490,6 +506,8 @@ def browser_start(
|
|
|
490
506
|
from typing import Any
|
|
491
507
|
|
|
492
508
|
launch_options: dict[str, Any] = {"chrome_path": chrome_path} if chrome_path else {}
|
|
509
|
+
if display is not None:
|
|
510
|
+
launch_options["display"] = display
|
|
493
511
|
for attempt in range(max_attempts):
|
|
494
512
|
process = launch_chrome(
|
|
495
513
|
port=port,
|
|
@@ -791,6 +809,10 @@ async def browser_connect(
|
|
|
791
809
|
tab_switch/tab_close; automation_target_id tracks the main page identity.
|
|
792
810
|
For visible local collaboration, show the dedicated page with tab_switch
|
|
793
811
|
using its targets index so the human and agent work on that same page.
|
|
812
|
+
Extension results report the loaded `extension_version` separately from
|
|
813
|
+
`bundled_extension_version`; an unknown version stays null. Updating Python
|
|
814
|
+
does not reload Chrome's extension. Ordinary extension reload can reset
|
|
815
|
+
control: inspect the session and original owned page before further input.
|
|
794
816
|
|
|
795
817
|
Args:
|
|
796
818
|
transport: "cdp" (default, existing debug Chrome) or "extension"
|
|
@@ -806,7 +828,8 @@ async def browser_connect(
|
|
|
806
828
|
Returns:
|
|
807
829
|
dict with connected, transport, session_id, capabilities, profile_id,
|
|
808
830
|
owner, profile_owner, actual headless (null if unknown), account for
|
|
809
|
-
extension, port for CDP, tabs and tab_count. Includes version/git_commit
|
|
831
|
+
extension, port for CDP, tabs and tab_count. Includes version/git_commit
|
|
832
|
+
and loaded/bundled extension versions with their observed match status.
|
|
810
833
|
|
|
811
834
|
Failure:
|
|
812
835
|
Read error_code and setup_instructions. Missing bridge/extension needs
|
|
@@ -933,6 +956,9 @@ async def browser_connect(
|
|
|
933
956
|
{"tab_id": str(t._target.target_id), "url": t._target.url or ""}
|
|
934
957
|
for t in browser.tabs
|
|
935
958
|
]
|
|
959
|
+
loaded_version = await query_extension_version(
|
|
960
|
+
browser, status.get("extension_version")
|
|
961
|
+
)
|
|
936
962
|
finally:
|
|
937
963
|
await browser.close()
|
|
938
964
|
main = status.get("automation_target_id")
|
|
@@ -951,6 +977,7 @@ async def browser_connect(
|
|
|
951
977
|
"profile_id": status["profile_id"],
|
|
952
978
|
"automation_target_id": main,
|
|
953
979
|
"browser_version": status.get("browser_version"),
|
|
980
|
+
**extension_version_info(loaded_version),
|
|
954
981
|
}
|
|
955
982
|
)
|
|
956
983
|
_session.write(record)
|
|
@@ -17,6 +17,7 @@ import shutil
|
|
|
17
17
|
import subprocess
|
|
18
18
|
import tempfile
|
|
19
19
|
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
20
21
|
|
|
21
22
|
from .config import DEFAULT_DEBUG_PORT, DEFAULT_PROFILE_PREFIX, resolve_viewport
|
|
22
23
|
|
|
@@ -155,6 +156,7 @@ def launch_chrome(
|
|
|
155
156
|
hide_crash_restore_bubble: bool = True,
|
|
156
157
|
stealth: bool = True,
|
|
157
158
|
chrome_path: str | None = None,
|
|
159
|
+
display: str | None = None,
|
|
158
160
|
) -> subprocess.Popen:
|
|
159
161
|
"""
|
|
160
162
|
Launch Chrome with remote debugging enabled.
|
|
@@ -338,10 +340,14 @@ def launch_chrome(
|
|
|
338
340
|
if start_url:
|
|
339
341
|
args.append(start_url)
|
|
340
342
|
|
|
343
|
+
if display is not None:
|
|
344
|
+
return _spawn_chrome(args, silent_stderr=silent_stderr, display=display)
|
|
341
345
|
return _spawn_chrome(args, silent_stderr=silent_stderr)
|
|
342
346
|
|
|
343
347
|
|
|
344
|
-
def _spawn_chrome(
|
|
348
|
+
def _spawn_chrome(
|
|
349
|
+
args: list[str], silent_stderr: bool = False, *, display: str | None = None
|
|
350
|
+
) -> subprocess.Popen:
|
|
345
351
|
"""Shared detached process primitive for CDP and configured-profile startup."""
|
|
346
352
|
# Start Chrome process
|
|
347
353
|
try:
|
|
@@ -349,6 +355,7 @@ def _spawn_chrome(args: list[str], silent_stderr: bool = False) -> subprocess.Po
|
|
|
349
355
|
# On Unix, use start_new_session to detach from parent
|
|
350
356
|
# On Windows, CREATE_NEW_PROCESS_GROUP for similar isolation
|
|
351
357
|
stderr_target = subprocess.DEVNULL if silent_stderr else subprocess.PIPE
|
|
358
|
+
popen_kwargs: dict[str, Any]
|
|
352
359
|
if platform.system() == "Windows":
|
|
353
360
|
popen_kwargs = {
|
|
354
361
|
"stdout": subprocess.DEVNULL,
|
|
@@ -362,6 +369,8 @@ def _spawn_chrome(args: list[str], silent_stderr: bool = False) -> subprocess.Po
|
|
|
362
369
|
"start_new_session": True,
|
|
363
370
|
}
|
|
364
371
|
|
|
372
|
+
if display is not None:
|
|
373
|
+
popen_kwargs["env"] = {**os.environ, "DISPLAY": display}
|
|
365
374
|
logger.debug("Launching Chrome executable %s", args[0])
|
|
366
375
|
process = subprocess.Popen(args, **popen_kwargs) # type: ignore[call-overload]
|
|
367
376
|
|
|
@@ -1239,6 +1239,9 @@ async def type_by_text(
|
|
|
1239
1239
|
`{typed, verified, method, name}` (plus `entered` when `enter=True`) —
|
|
1240
1240
|
`verified` already confirms the value landed, so don't re-read to check.
|
|
1241
1241
|
|
|
1242
|
+
In extension mode, hidden-page or ownership refusals stop the operation
|
|
1243
|
+
before clearing; input fallbacks never override these refusals.
|
|
1244
|
+
|
|
1242
1245
|
Prefer over `type_by_ref` when you can identify the input by its
|
|
1243
1246
|
human-visible label rather than needing a prior `page_discover` ref. The
|
|
1244
1247
|
`clear` / `enter` / `keystrokes` / `human_like` options mirror `type_by_ref`
|
|
@@ -27,8 +27,19 @@ class SessionError(Exception):
|
|
|
27
27
|
class InputUnavailableError(SessionError):
|
|
28
28
|
"""A requested input was withheld because its page could not be made visible."""
|
|
29
29
|
|
|
30
|
-
def __init__(self, message: str):
|
|
30
|
+
def __init__(self, message: str, *, details: dict | None = None):
|
|
31
31
|
super().__init__(message, error_code="unsupported")
|
|
32
|
+
self.details = details or {}
|
|
33
|
+
self.hint = (
|
|
34
|
+
"This input dispatch was not sent. Show the selected Chrome tab/window "
|
|
35
|
+
"and inspect current state before resuming; earlier steps may have "
|
|
36
|
+
"completed. If the loaded "
|
|
37
|
+
"extension version is unknown or differs from the bundle, arrange an "
|
|
38
|
+
"ordinary update/reload in chrome://extensions; reloading can reset "
|
|
39
|
+
"control, so inspect session ownership again afterwards. Do not replay "
|
|
40
|
+
"earlier submissions, spoof visibility/focus, switch transport or use "
|
|
41
|
+
"OS input to bypass the refusal."
|
|
42
|
+
)
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
class RecordingError(Exception):
|
|
@@ -180,7 +180,7 @@ class _Bridge:
|
|
|
180
180
|
# ------------------------------------------------------------------ drivers
|
|
181
181
|
_LOGGED = (
|
|
182
182
|
"Page.navigate",
|
|
183
|
-
"Input.
|
|
183
|
+
"Input.", # Command names only: never include typed text in the log.
|
|
184
184
|
"Runtime.evaluate",
|
|
185
185
|
"Runtime.callFunctionOn",
|
|
186
186
|
)
|
|
@@ -10,6 +10,7 @@ package ships and the steps to relay to the user.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from pathlib import Path
|
|
13
|
+
import json
|
|
13
14
|
import os
|
|
14
15
|
|
|
15
16
|
# Fixed local port the extension's background.js dials and the adb-side bridge
|
|
@@ -26,6 +27,34 @@ def extension_dir() -> Path:
|
|
|
26
27
|
return Path(__file__).resolve().parent.parent / "extension"
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
def extension_version_info(loaded_version: str | None) -> dict:
|
|
31
|
+
"""Compare a running extension's report with the bundled manifest, not Python."""
|
|
32
|
+
bundled = json.loads(
|
|
33
|
+
(extension_dir() / "manifest.json").read_text(encoding="utf-8")
|
|
34
|
+
)["version"]
|
|
35
|
+
return {
|
|
36
|
+
"extension_version": loaded_version,
|
|
37
|
+
"bundled_extension_version": bundled,
|
|
38
|
+
"extension_version_matches_bundle": loaded_version == bundled
|
|
39
|
+
if loaded_version
|
|
40
|
+
else None,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def query_extension_version(browser, fallback: str | None = None) -> str | None:
|
|
45
|
+
"""Read the live extension, including when an older bridge drops hello fields."""
|
|
46
|
+
from .cdp import _get_cdp_command
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
state = await browser.connection.send(
|
|
50
|
+
_get_cdp_command("AiDevBrowser.debugState", {}), timeout=1
|
|
51
|
+
)
|
|
52
|
+
version = state.get("extensionVersion")
|
|
53
|
+
return version if isinstance(version, str) else fallback
|
|
54
|
+
except Exception:
|
|
55
|
+
return fallback # Diagnostics never replace the actual operation's error.
|
|
56
|
+
|
|
57
|
+
|
|
29
58
|
def extension_load_instructions(extension_dir_path: str | None = None) -> str:
|
|
30
59
|
"""Step-by-step 'Load unpacked' guidance, with the resolved package path.
|
|
31
60
|
|