ai-dev-browser 0.58.2__tar.gz → 0.58.4__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.
Files changed (374) hide show
  1. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.github/workflows/ci.yml +24 -4
  2. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/PKG-INFO +11 -2
  3. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/README.md +10 -1
  4. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_recorder.py +27 -13
  5. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_session.py +1 -0
  6. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_transport.py +12 -9
  7. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/browser.py +166 -21
  8. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/connection.py +4 -0
  9. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/ext_bridge.py +6 -3
  10. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/extension.py +21 -3
  11. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/port.py +56 -15
  12. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/background.js +15 -4
  13. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/manifest.json +1 -1
  14. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/page_view.js +2 -2
  15. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser.egg-info/PKG-INFO +11 -2
  16. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser.egg-info/SOURCES.txt +10 -0
  17. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser.egg-info/scm_file_list.json +10 -0
  18. ai_dev_browser-0.58.4/docs/BR-cdp-startup-target-20261024.md +106 -0
  19. ai_dev_browser-0.58.4/docs/BR-extension-runtime-20261023-resolution.md +117 -0
  20. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/docs/remote-views.md +22 -4
  21. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/pyproject.toml +2 -0
  22. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/conftest.py +113 -7
  23. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/extension_ownership_support.py +18 -0
  24. ai_dev_browser-0.58.4/tests/integration/extension_runtime_support.py +232 -0
  25. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/extension_window_support.py +60 -0
  26. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/live_cleanup.py +5 -2
  27. ai_dev_browser-0.58.4/tests/integration/live_resources.py +58 -0
  28. ai_dev_browser-0.58.4/tests/integration/navigation_startup_support.py +432 -0
  29. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/recording_demo_support.py +150 -5
  30. ai_dev_browser-0.58.4/tests/integration/scenarios_extension_runtime.json +22 -0
  31. ai_dev_browser-0.58.4/tests/integration/scenarios_extension_windowless.json +22 -0
  32. ai_dev_browser-0.58.4/tests/integration/scenarios_navigation_startup.json +78 -0
  33. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_recording_demo.json +46 -2
  34. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_start_retry.py +28 -6
  35. ai_dev_browser-0.58.4/tests/integration/test_discovery_socket_pressure.py +108 -0
  36. ai_dev_browser-0.58.4/tests/integration/test_extension_runtime_upgrade.py +67 -0
  37. ai_dev_browser-0.58.4/tests/integration/test_extension_windowless.py +69 -0
  38. ai_dev_browser-0.58.4/tests/integration/test_navigation_startup.py +141 -0
  39. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_recording_demo.py +44 -2
  40. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_remote_selection_live.py +243 -39
  41. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_remote_view.py +3 -1
  42. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/extension_ownership.test.cjs +65 -0
  43. ai_dev_browser-0.58.4/tests/unit/test_discovery_budget.py +47 -0
  44. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_lifecycle_handshake_errors.py +91 -5
  45. ai_dev_browser-0.58.4/tests/unit/test_live_cleanup.py +135 -0
  46. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_window_input_contract.py +8 -0
  47. ai_dev_browser-0.58.2/tests/integration/navigation_startup_support.py +0 -116
  48. ai_dev_browser-0.58.2/tests/integration/scenarios_navigation_startup.json +0 -50
  49. ai_dev_browser-0.58.2/tests/integration/test_navigation_startup.py +0 -81
  50. ai_dev_browser-0.58.2/tests/unit/test_live_cleanup.py +0 -65
  51. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.dockerignore +0 -0
  52. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.gitattributes +0 -0
  53. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.github/workflows/publish.yml +0 -0
  54. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.gitignore +0 -0
  55. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.gitmodules +0 -0
  56. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.pre-commit-config.yaml +0 -0
  57. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/.secrets.baseline +0 -0
  58. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/LICENSE +0 -0
  59. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/__init__.py +0 -0
  60. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/_cli.py +0 -0
  61. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/_version.py +0 -0
  62. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/__init__.py +0 -0
  63. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/accessibility.py +0 -0
  64. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/animation.py +0 -0
  65. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/audits.py +0 -0
  66. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/autofill.py +0 -0
  67. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/background_service.py +0 -0
  68. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/bluetooth_emulation.py +0 -0
  69. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/browser.py +0 -0
  70. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/cache_storage.py +0 -0
  71. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/cast.py +0 -0
  72. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/console.py +0 -0
  73. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/crash_report_context.py +0 -0
  74. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/css.py +0 -0
  75. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/debugger.py +0 -0
  76. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/device_access.py +0 -0
  77. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/device_orientation.py +0 -0
  78. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/dom.py +0 -0
  79. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/dom_debugger.py +0 -0
  80. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/dom_snapshot.py +0 -0
  81. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/dom_storage.py +0 -0
  82. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/emulation.py +0 -0
  83. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/event_breakpoints.py +0 -0
  84. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/extensions.py +0 -0
  85. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/fed_cm.py +0 -0
  86. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/fetch.py +0 -0
  87. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/file_system.py +0 -0
  88. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/headless_experimental.py +0 -0
  89. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/heap_profiler.py +0 -0
  90. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/indexed_db.py +0 -0
  91. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/input_.py +0 -0
  92. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/inspector.py +0 -0
  93. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/io.py +0 -0
  94. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/layer_tree.py +0 -0
  95. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/log.py +0 -0
  96. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/media.py +0 -0
  97. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/memory.py +0 -0
  98. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/network.py +0 -0
  99. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/overlay.py +0 -0
  100. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/page.py +0 -0
  101. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/performance.py +0 -0
  102. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/performance_timeline.py +0 -0
  103. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/preload.py +0 -0
  104. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/profiler.py +0 -0
  105. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/pwa.py +0 -0
  106. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/py.typed +0 -0
  107. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/runtime.py +0 -0
  108. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/schema.py +0 -0
  109. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/security.py +0 -0
  110. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/service_worker.py +0 -0
  111. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/smart_card_emulation.py +0 -0
  112. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/storage.py +0 -0
  113. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/system_info.py +0 -0
  114. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/target.py +0 -0
  115. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/tethering.py +0 -0
  116. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/tracing.py +0 -0
  117. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/util.py +0 -0
  118. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/web_audio.py +0 -0
  119. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/web_authn.py +0 -0
  120. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/cdp/web_mcp.py +0 -0
  121. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/__init__.py +0 -0
  122. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_browser_guidance.py +0 -0
  123. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_case.py +0 -0
  124. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_demo.py +0 -0
  125. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_element.py +0 -0
  126. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_extension_lifecycle.py +0 -0
  127. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_image_cap.py +0 -0
  128. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_js.py +0 -0
  129. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_ref.py +0 -0
  130. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/_tab.py +0 -0
  131. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/ax.py +0 -0
  132. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/cdp.py +0 -0
  133. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/chrome.py +0 -0
  134. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/cleanup.py +0 -0
  135. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/config.py +0 -0
  136. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/cookies.py +0 -0
  137. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/cookies_import.py +0 -0
  138. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/dialog.py +0 -0
  139. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/download.py +0 -0
  140. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/elements.py +0 -0
  141. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/errors.py +0 -0
  142. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/human.py +0 -0
  143. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/identity.py +0 -0
  144. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/login.py +0 -0
  145. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/mouse.py +0 -0
  146. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/navigation.py +0 -0
  147. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/overlays.py +0 -0
  148. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/page.py +0 -0
  149. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/process.py +0 -0
  150. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/recording.py +0 -0
  151. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/registry.py +0 -0
  152. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/snapshot.py +0 -0
  153. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/storage.py +0 -0
  154. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/tabs.py +0 -0
  155. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/text_match.py +0 -0
  156. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/core/window.py +0 -0
  157. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/README.md +0 -0
  158. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/icon128.png +0 -0
  159. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/icon16.png +0 -0
  160. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/icon48.png +0 -0
  161. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/extension/managed-schema.json +0 -0
  162. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/pool/__init__.py +0 -0
  163. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/pool/job.py +0 -0
  164. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/pool/persistence.py +0 -0
  165. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/pool/pool.py +0 -0
  166. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/pool/worker.py +0 -0
  167. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/profile.py +0 -0
  168. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/py.typed +0 -0
  169. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/__init__.py +0 -0
  170. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/cdp.py +0 -0
  171. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/control.py +0 -0
  172. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/existing.py +0 -0
  173. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/files.py +0 -0
  174. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/mcp-tools.json +0 -0
  175. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/page.py +0 -0
  176. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/profile_lease.py +0 -0
  177. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/provision.py +0 -0
  178. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/session.py +0 -0
  179. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/vnc.py +0 -0
  180. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/window.py +0 -0
  181. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/remote/worker.py +0 -0
  182. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/__init__.py +0 -0
  183. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/_generate.py +0 -0
  184. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_cleanup.py +0 -0
  185. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_connect.py +0 -0
  186. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_disconnect.py +0 -0
  187. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_list.py +0 -0
  188. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_reconnect.py +0 -0
  189. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_restart.py +0 -0
  190. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_start.py +0 -0
  191. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/browser_stop.py +0 -0
  192. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/cdp_send.py +0 -0
  193. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/click_by_html_id.py +0 -0
  194. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/click_by_ref.py +0 -0
  195. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/click_by_text.py +0 -0
  196. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/click_by_xpath.py +0 -0
  197. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/click_row_by_text.py +0 -0
  198. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/cookies_extract_live.py +0 -0
  199. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/cookies_extract_offline.py +0 -0
  200. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/cookies_import.py +0 -0
  201. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/cookies_load.py +0 -0
  202. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/cookies_save.py +0 -0
  203. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/dialog_respond.py +0 -0
  204. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/download.py +0 -0
  205. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/download_link.py +0 -0
  206. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/drag_by_ref.py +0 -0
  207. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/find_by_html_id.py +0 -0
  208. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/find_by_text.py +0 -0
  209. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/find_by_xpath.py +0 -0
  210. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/focus_by_ref.py +0 -0
  211. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/highlight_by_ref.py +0 -0
  212. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/hover_by_ref.py +0 -0
  213. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/html_by_ref.py +0 -0
  214. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/js_evaluate.py +0 -0
  215. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/login_interactive.py +0 -0
  216. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/mouse_click.py +0 -0
  217. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/mouse_drag.py +0 -0
  218. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/mouse_move.py +0 -0
  219. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_discover.py +0 -0
  220. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_emulate_focus.py +0 -0
  221. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_goto.py +0 -0
  222. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_html.py +0 -0
  223. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_info.py +0 -0
  224. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_pdf.py +0 -0
  225. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_record_start.py +0 -0
  226. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_record_stop.py +0 -0
  227. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_reload.py +0 -0
  228. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_screenshot.py +0 -0
  229. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_scroll.py +0 -0
  230. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_wait_element.py +0 -0
  231. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_wait_ready.py +0 -0
  232. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/page_wait_url.py +0 -0
  233. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/press_key.py +0 -0
  234. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/screenshot_by_ref.py +0 -0
  235. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/select_by_ref.py +0 -0
  236. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/select_text.py +0 -0
  237. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/storage_get.py +0 -0
  238. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/storage_set.py +0 -0
  239. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/tab_close.py +0 -0
  240. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/tab_list.py +0 -0
  241. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/tab_new.py +0 -0
  242. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/tab_switch.py +0 -0
  243. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/type_by_ref.py +0 -0
  244. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/type_by_text.py +0 -0
  245. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/upload_by_ref.py +0 -0
  246. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser/tools/window_set.py +0 -0
  247. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser.egg-info/dependency_links.txt +0 -0
  248. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser.egg-info/requires.txt +0 -0
  249. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/ai_dev_browser.egg-info/top_level.txt +0 -0
  250. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/deploy/managed-chrome/Dockerfile +0 -0
  251. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/deploy/managed-chrome/configure_extension.py +0 -0
  252. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/docs/BR-real-profile-hidden-input-resolution.md +0 -0
  253. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/docs/navigation-ownership.md +0 -0
  254. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/scripts/build_release_assets.py +0 -0
  255. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/scripts/live_browser_lifecycle_steering.py +0 -0
  256. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/scripts/live_grok_pool_persistence.py +0 -0
  257. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/scripts/live_recording_steering.py +0 -0
  258. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/scripts/sync_cdp.py +0 -0
  259. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/setup.cfg +0 -0
  260. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/__init__.py +0 -0
  261. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/conftest.py +0 -0
  262. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/__init__.py +0 -0
  263. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/browser_lifecycle_acceptance.json +0 -0
  264. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/browser_lifecycle_journeys.py +0 -0
  265. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/browser_lifecycle_support.py +0 -0
  266. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/browser_lifecycle_ui.py +0 -0
  267. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/cli_steering_acceptance.json +0 -0
  268. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/cli_steering_support.py +0 -0
  269. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/native_window_occluder.py +0 -0
  270. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/pdf_readiness_support.py +0 -0
  271. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/pool-persistence-acceptance.json +0 -0
  272. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/recording_demo_acceptance.json +0 -0
  273. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/run_managed_extension.py +0 -0
  274. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_browser_lifecycle.json +0 -0
  275. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_cli_extension.json +0 -0
  276. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_cli_steering.json +0 -0
  277. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_extension_ownership.json +0 -0
  278. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_extension_ownership_native.json +0 -0
  279. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_extension_window.json +0 -0
  280. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_extension_window_previous.json +0 -0
  281. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_pdf_readiness.json +0 -0
  282. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_public_web.json +0 -0
  283. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_recording.json +0 -0
  284. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/scenarios_workspace.json +0 -0
  285. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_background_processes.py +0 -0
  286. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_control_live.py +0 -0
  287. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_files_live.py +0 -0
  288. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_inventory.py +0 -0
  289. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_lifecycle.py +0 -0
  290. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_session_live.py +0 -0
  291. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_browser_worker_live.py +0 -0
  292. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cdp_send_warning.py +0 -0
  293. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_ci_integration.py +0 -0
  294. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cli_extension_workflows.py +0 -0
  295. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cli_steering.py +0 -0
  296. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cli_unicode_help.py +0 -0
  297. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_click_robust.py +0 -0
  298. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_config_resolvers.py +0 -0
  299. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cookies_extract_hermetic.py +0 -0
  300. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cookies_extract_live.py +0 -0
  301. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cookies_extract_windows.py +0 -0
  302. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_cookies_import.py +0 -0
  303. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_dialog_auto.py +0 -0
  304. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_dialog_respond.py +0 -0
  305. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_dom_discovery.py +0 -0
  306. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_download_link.py +0 -0
  307. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_download_link_frame.py +0 -0
  308. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_existing_worker_live.py +0 -0
  309. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_ext_bridge.py +0 -0
  310. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_extension_ownership.py +0 -0
  311. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_extension_ownership_native.py +0 -0
  312. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_extension_window_native.py +0 -0
  313. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_extension_window_previous.py +0 -0
  314. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_find_and_interact_workflows.py +0 -0
  315. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_image_cap.py +0 -0
  316. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_js_contract.py +0 -0
  317. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_js_evaluate_frame.py +0 -0
  318. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_locator_workflows.py +0 -0
  319. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_mouse_click.py +0 -0
  320. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_mouse_drag_move.py +0 -0
  321. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_multi_profile_pool.py +0 -0
  322. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_page_discover.py +0 -0
  323. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_page_goto.py +0 -0
  324. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_page_pdf.py +0 -0
  325. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_page_reload.py +0 -0
  326. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_page_scroll_verify.py +0 -0
  327. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_page_wait_element.py +0 -0
  328. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_pdf_readiness.py +0 -0
  329. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_pool_model_persistence.py +0 -0
  330. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_port_detection.py +0 -0
  331. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_press_key.py +0 -0
  332. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_proxy_identity.py +0 -0
  333. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_public_web_workflows.py +0 -0
  334. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_recording_workflows.py +0 -0
  335. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_remote_vnc.py +0 -0
  336. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_remote_window_live.py +0 -0
  337. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_screenshot_coords.py +0 -0
  338. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_scroll_embedded_target.py +0 -0
  339. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_select_text.py +0 -0
  340. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_startup_timeout.py +0 -0
  341. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_stealth_defaults.py +0 -0
  342. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_tab_close.py +0 -0
  343. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_tab_url_matching.py +0 -0
  344. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_text_locator.py +0 -0
  345. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_timeout_and_retry.py +0 -0
  346. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_trusted_click.py +0 -0
  347. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_type_verified_fill.py +0 -0
  348. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_viewport.py +0 -0
  349. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_worker_managed_status_live.py +0 -0
  350. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/integration/test_workspace_workflows.py +0 -0
  351. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_browser_control.py +0 -0
  352. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_build_info.py +0 -0
  353. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_case.py +0 -0
  354. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cdp_coerce.py +0 -0
  355. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cdp_session_scoped.py +0 -0
  356. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cdp_unbound.py +0 -0
  357. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cli_docstring_args.py +0 -0
  358. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cli_error_envelope.py +0 -0
  359. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cli_param_type.py +0 -0
  360. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_cli_steering_contract.py +0 -0
  361. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_demo_encoding.py +0 -0
  362. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_extension_ownership.py +0 -0
  363. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_find_hint.py +0 -0
  364. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_launch_window_size.py +0 -0
  365. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_no_acronym_rot.py +0 -0
  366. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_pdf_units.py +0 -0
  367. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_recording.py +0 -0
  368. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_release_version.py +0 -0
  369. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_remote_files.py +0 -0
  370. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_remote_navigation.py +0 -0
  371. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_session_failure_state.py +0 -0
  372. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_tool_registry.py +0 -0
  373. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/tests/unit/test_worker_lifecycle_status.py +0 -0
  374. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.4}/uv.lock +0 -0
@@ -167,13 +167,13 @@ jobs:
167
167
  env:
168
168
  AI_DEV_BROWSER_CHROME: ${{ steps.chrome.outputs.chrome-path }}
169
169
  AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
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
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_discovery_socket_pressure.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
171
171
  - name: Run page and extension workflows on Windows
172
172
  if: runner.os == 'Windows'
173
173
  env:
174
174
  AI_DEV_BROWSER_CHROME: ${{ steps.chrome.outputs.chrome-path }}
175
175
  AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
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
176
+ run: python -m pytest -v --full-trace -o faulthandler_timeout=180 tests/integration/test_remote_view.py tests/integration/test_discovery_socket_pressure.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
177
177
  - name: Retain real viewer evidence
178
178
  if: always()
179
179
  uses: actions/upload-artifact@v4
@@ -193,7 +193,7 @@ jobs:
193
193
  run: docker build -f deploy/managed-chrome/Dockerfile --secret "id=extension_key,src=$RUNNER_TEMP/extension-fixture.pem" --build-arg "SOURCE_REVISION=$GITHUB_SHA" -t adb-managed-chrome:ci .
194
194
  - name: Run real signed extension, handoff and viewer workflows
195
195
  run: |
196
- docker create --name "adb-managed-live-$GITHUB_RUN_ID" --memory=1536m --shm-size=256m --pids-limit=384 \
196
+ docker create --init --name "adb-managed-live-$GITHUB_RUN_ID" --memory=1536m --shm-size=256m --pids-limit=384 \
197
197
  --mount "type=bind,source=$PWD/tests,target=/opt/browser/tests,readonly" \
198
198
  adb-managed-chrome:ci /bin/sleep infinity
199
199
  docker start "adb-managed-live-$GITHUB_RUN_ID"
@@ -250,7 +250,7 @@ jobs:
250
250
  - name: Run the real extension on both WebSocket server implementations
251
251
  env:
252
252
  AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
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
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 tests/integration/test_extension_windowless.py -k "real_extension or outbound_proxy" --basetemp extension-results
254
254
 
255
255
  - name: Install the unchanged released extension fixture
256
256
  run: uv pip install --target previous-extension-fixture --no-deps ai-dev-browser==0.57.7
@@ -261,6 +261,15 @@ jobs:
261
261
  AI_DEV_BROWSER_TEST_PREVIOUS_EXTENSION: ${{ github.workspace }}/previous-extension-fixture/ai_dev_browser/extension
262
262
  run: uv run --no-sync python -m pytest -v tests/integration/test_extension_window_previous.py --basetemp previous-extension-results
263
263
 
264
+ - name: Install the released extension without version reporting
265
+ run: uv pip install --target unknown-extension-fixture --no-deps ai-dev-browser==0.54.0
266
+
267
+ - name: Recover an unknown extension runtime through an ordinary reload
268
+ env:
269
+ AI_DEV_BROWSER_TEST_EXTENSION_CHROME: ${{ steps.chrome.outputs.chrome-path }}
270
+ AI_DEV_BROWSER_TEST_UNKNOWN_EXTENSION: ${{ github.workspace }}/unknown-extension-fixture/ai_dev_browser/extension
271
+ run: uv run --no-sync python -m pytest -v tests/integration/test_extension_runtime_upgrade.py --basetemp runtime-upgrade-results
272
+
264
273
  - name: Honor genuine Windows window visibility before input
265
274
  if: runner.os == 'Windows'
266
275
  env:
@@ -300,13 +309,24 @@ jobs:
300
309
  extension-results/**/extension-lifecycle.png
301
310
  extension-results/**/installed-extension.png
302
311
  extension-results/**/extension-startup-evidence.json
312
+ extension-results/**/windowless-startup-order.json
313
+ extension-results/**/windowless-extension-accepted.png
303
314
  extension-results/**/extension-chrome.log
315
+ extension-results/**/host-resources-*.json
316
+ extension-results/**/navigation-readiness.json
304
317
  extension-results/**/extension-cleanup-stacks.txt
305
318
  native-ownership-results/**
306
319
  native-window-results/**/native-window-evidence.json
307
320
  native-window-results/**/native-window-accepted.png
308
321
  native-window-results/**/extension-chrome.log
322
+ native-window-results/**/host-resources-*.json
323
+ native-window-results/**/extension-cleanup-evidence.json
324
+ native-window-results/**/extension-cleanup-stacks.txt
309
325
  previous-extension-results/**/previous-extension-evidence.json
326
+ runtime-upgrade-results/**/runtime-upgrade-evidence.json
327
+ runtime-upgrade-results/**/runtime-upgrade-accepted.png
328
+ runtime-upgrade-results/**/extension-*.json
329
+ runtime-upgrade-results/**/extension-chrome.log
310
330
 
311
331
  # ─────────────────────────────────────────────────────────────────────
312
332
  # 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.2
3
+ Version: 0.58.4
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
@@ -94,6 +94,11 @@ python -m ai_dev_browser.tools.browser_disconnect --session-id <returned-id>
94
94
  python -m ai_dev_browser.tools.browser_reconnect --session-id <returned-id>
95
95
  ```
96
96
 
97
+ Fresh CDP launches prepare a blank owned page before navigating once to the
98
+ requested URL, and return that exact page. If page
99
+ creation is uncertain, Chrome remains running; connect to the reported port and
100
+ inspect its pages before continuing.
101
+
97
102
  For extension startup, first install and enable the bundled bridge in the chosen
98
103
  Chrome profile. Select `--chrome-path`, `--user-data-dir`, `--profile-directory`
99
104
  and `--headless` explicitly as needed. Startup adds neither a debugging port nor
@@ -453,11 +458,15 @@ and [GitHub Releases](https://github.com/sudoprivacy/ai-dev-browser/releases).
453
458
  Each GitHub release includes the same wheel and source distribution sent to PyPI,
454
459
  the bundled Chrome extension as an unpackable ZIP, and `SHA256SUMS`.
455
460
  For extension mode, extract the ZIP and load its directory in `chrome://extensions`.
456
- After upgrading, reload the extension and restart an already running bridge.
461
+ After upgrading, ordinarily reload the extension in the intended Chrome profile,
462
+ then reconnect with `browser_connect --transport extension`.
457
463
  `browser_connect` reports the actual loaded `extension_version`, the
458
464
  `bundled_extension_version`, and whether they match. Updating Python alone does
459
465
  not reload Chrome's extension. Reload can reset control; inspect the session and
460
466
  its owned page before resuming input.
467
+ `extension_warning` is non-null when the loaded version is unknown or differs
468
+ from the bundle. Follow its steps before input; `connected=true` confirms a
469
+ connection. Version matching and actual page visibility are separate checks.
461
470
 
462
471
  For a hidden extension page, `window_set --state normal --focus` requests real
463
472
  window restoration and reports observed `visibility`, `focused`, and
@@ -52,6 +52,11 @@ python -m ai_dev_browser.tools.browser_disconnect --session-id <returned-id>
52
52
  python -m ai_dev_browser.tools.browser_reconnect --session-id <returned-id>
53
53
  ```
54
54
 
55
+ Fresh CDP launches prepare a blank owned page before navigating once to the
56
+ requested URL, and return that exact page. If page
57
+ creation is uncertain, Chrome remains running; connect to the reported port and
58
+ inspect its pages before continuing.
59
+
55
60
  For extension startup, first install and enable the bundled bridge in the chosen
56
61
  Chrome profile. Select `--chrome-path`, `--user-data-dir`, `--profile-directory`
57
62
  and `--headless` explicitly as needed. Startup adds neither a debugging port nor
@@ -411,11 +416,15 @@ and [GitHub Releases](https://github.com/sudoprivacy/ai-dev-browser/releases).
411
416
  Each GitHub release includes the same wheel and source distribution sent to PyPI,
412
417
  the bundled Chrome extension as an unpackable ZIP, and `SHA256SUMS`.
413
418
  For extension mode, extract the ZIP and load its directory in `chrome://extensions`.
414
- After upgrading, reload the extension and restart an already running bridge.
419
+ After upgrading, ordinarily reload the extension in the intended Chrome profile,
420
+ then reconnect with `browser_connect --transport extension`.
415
421
  `browser_connect` reports the actual loaded `extension_version`, the
416
422
  `bundled_extension_version`, and whether they match. Updating Python alone does
417
423
  not reload Chrome's extension. Reload can reset control; inspect the session and
418
424
  its owned page before resuming input.
425
+ `extension_warning` is non-null when the loaded version is unknown or differs
426
+ from the bundle. Follow its steps before input; `connected=true` confirms a
427
+ connection. Version matching and actual page visibility are separate checks.
419
428
 
420
429
  For a hidden extension page, `window_set --state normal --focus` requests real
421
430
  window restoration and reports observed `visibility`, `focused`, and
@@ -17,6 +17,7 @@ import websockets
17
17
  from PIL import GifImagePlugin, Image, ImageChops, ImageOps
18
18
 
19
19
  from ._demo import Overlay, index_path
20
+ from ._transport import _local_websocket_options
20
21
  from .errors import RecordingError
21
22
  from .recording import _read_json, _write_json
22
23
 
@@ -212,7 +213,11 @@ class _Capture:
212
213
 
213
214
  async def _record(folder: Path, config: dict, partial: Path) -> dict:
214
215
  async with websockets.connect(
215
- config["url"], max_size=16 * 1024 * 1024, open_timeout=5, close_timeout=2
216
+ config["url"],
217
+ max_size=16 * 1024 * 1024,
218
+ open_timeout=5,
219
+ close_timeout=2,
220
+ **_local_websocket_options(config["url"]),
216
221
  ) as ws:
217
222
  capture = _Capture(ws)
218
223
  receiver = asyncio.create_task(capture.receive())
@@ -273,13 +278,23 @@ async def _record(folder: Path, config: dict, partial: Path) -> dict:
273
278
  source, timestamp, metadata or capture.metadata
274
279
  )
275
280
 
281
+ health_metrics = None
282
+
283
+ async def sample_health():
284
+ nonlocal health_metrics
285
+ while True:
286
+ # A quiet page emits no frames. Check target liveness on
287
+ # its own clock: a slow RPC must not stop cursor animation.
288
+ health_metrics = await capture.command("Page.getLayoutMetrics")
289
+ await asyncio.sleep(0.2)
290
+
291
+ tasks.append(asyncio.create_task(sample_health()))
276
292
  with partial.open("xb") as output:
277
293
  gif = _Gif(output)
278
294
  data, started = capture.latest
279
295
  await asyncio.to_thread(gif.push, render(data, started), started)
280
296
  last_frame = started
281
297
  last_received = started
282
- last_health = 0.0
283
298
  last_state = 0.0
284
299
  while True:
285
300
  if (folder / "abort").exists():
@@ -289,30 +304,29 @@ async def _record(folder: Path, config: dict, partial: Path) -> dict:
289
304
  for task in tasks:
290
305
  if task.done():
291
306
  task.result()
292
- now = time.monotonic()
293
307
  requested_viewport = _read_json(folder / "viewport.json")
294
308
  if requested_viewport != viewport:
295
309
  await capture.command(
296
310
  "Emulation.setDeviceMetricsOverride", requested_viewport
297
311
  )
298
312
  viewport = requested_viewport
299
- if now - started >= config["max_duration"]:
300
- raise RecordingError(
301
- "Recording reached max_duration before stop; no GIF published"
302
- )
303
- if now - last_health >= 0.2:
304
- # Quiet pages legitimately emit no frames. Probe the target
305
- # separately so a detached extension cannot look like idle.
306
- metrics = await capture.command("Page.getLayoutMetrics")
307
- actual = metrics["cssLayoutViewport"]
313
+ if health_metrics is not None:
314
+ actual = health_metrics["cssLayoutViewport"]
315
+ health_metrics = None
308
316
  if (actual["clientWidth"], actual["clientHeight"]) != (
309
317
  viewport["width"],
310
318
  viewport["height"],
311
319
  ):
320
+ # Keep viewport writes on this one task, including
321
+ # explicit resize requests and navigation recovery.
312
322
  await capture.command(
313
323
  "Emulation.setDeviceMetricsOverride", viewport
314
324
  )
315
- last_health = now
325
+ now = time.monotonic()
326
+ if now - started >= config["max_duration"]:
327
+ raise RecordingError(
328
+ "Recording reached max_duration before stop; no GIF published"
329
+ )
316
330
  if now - last_state >= 0.5:
317
331
  _write_json(
318
332
  folder / "state.json",
@@ -242,6 +242,7 @@ def describe(record: dict) -> dict:
242
242
  "extension_version",
243
243
  "bundled_extension_version",
244
244
  "extension_version_matches_bundle",
245
+ "extension_warning",
245
246
  )
246
247
  if record.get("transport") == "extension"
247
248
  }
@@ -163,6 +163,17 @@ def _cdp_get_module(domain: str | types.ModuleType):
163
163
  return importlib.import_module(domain)
164
164
 
165
165
 
166
+ def _local_websocket_options(url: str) -> dict:
167
+ """Keep loopback control direct on proxy-aware websockets versions."""
168
+ if _is_loopback_host(urlsplit(url).hostname or "") and (
169
+ "proxy" in inspect.signature(websockets.connect).parameters
170
+ ):
171
+ # websockets 15+ discovers environment proxies by default. Older
172
+ # supported versions have no proxy parameter and connect directly.
173
+ return {"proxy": None}
174
+ return {}
175
+
176
+
166
177
  class CDPConnection:
167
178
  """Async WebSocket connection to a CDP endpoint.
168
179
 
@@ -189,19 +200,11 @@ class CDPConnection:
189
200
  """Open WebSocket and start listener."""
190
201
  if self._websocket and not self._websocket.close_code:
191
202
  return # already connected
192
- options = {}
193
- if _is_loopback_host(urlsplit(self.websocket_url).hostname or "") and (
194
- "proxy" in inspect.signature(websockets.connect).parameters
195
- ):
196
- # websockets 15+ discovers environment proxies by default. Local
197
- # Chrome/extension control is direct; older supported versions have
198
- # no proxy parameter and already connect directly.
199
- options["proxy"] = None
200
203
  self._websocket = await websockets.connect(
201
204
  self.websocket_url,
202
205
  ping_timeout=PING_TIMEOUT,
203
206
  max_size=MAX_SIZE,
204
- **options,
207
+ **_local_websocket_options(self.websocket_url),
205
208
  )
206
209
  self._listener_task = asyncio.ensure_future(self._listener())
207
210
  await self._register_handlers()
@@ -144,7 +144,9 @@ def _resolve_do_match(
144
144
  return env_match if env_match is not None else _proxy_in_args(extra_args)
145
145
 
146
146
 
147
- async def _read_navigator_language(port: int) -> str | None:
147
+ async def _read_navigator_language(
148
+ port: int, target_id: str | None = None
149
+ ) -> str | None:
148
150
  """The locale actually in effect — `navigator.language` read back from the
149
151
  launched browser. Chrome maps --lang to its nearest supported UI locale, so
150
152
  this is what a site sees, which may differ from the requested locale.
@@ -156,7 +158,11 @@ async def _read_navigator_language(port: int) -> str | None:
156
158
  browser = None
157
159
  try:
158
160
  browser = await connect_browser(port=port)
159
- tab = await get_active_tab(browser)
161
+ tab = (
162
+ next(t for t in browser.tabs if str(t._target.target_id) == target_id)
163
+ if target_id
164
+ else await get_active_tab(browser)
165
+ )
160
166
  val = await tab.evaluate("navigator.language")
161
167
  return val if isinstance(val, str) and val else None
162
168
  except Exception:
@@ -227,6 +233,9 @@ def browser_start(
227
233
  data directory returns conflict; use browser_connect instead. Startup
228
234
  changes neither installed extensions nor profile preferences. A failed
229
235
  handshake leaves the explicitly launched Chrome running and reports its PID.
236
+ Read extension_warning before input: an attached extension's unknown or
237
+ mismatched version needs an ordinary update/reload and a fresh connection.
238
+ Fresh CDP launches prepare their owned page before the initial URL navigation.
230
239
 
231
240
  Proxy consistency (automatic): if `extra_args` carries a `--proxy-server`
232
241
  and you don't pass an explicit `timezone`/`geo`, this auto-aligns the
@@ -267,8 +276,10 @@ def browser_start(
267
276
  reuse: "none" (always new) or "any" (reuse an idle same-profile
268
277
  Chrome, default). Only consulted for a named profile — an
269
278
  ephemeral session never reuses.
270
- startup_timeout: Seconds to wait for Chrome's debug port and CDP browser
271
- identity, or the configured extension handshake. `None` resolves to the
279
+ startup_timeout: Seconds to wait for Chrome's debug port, CDP browser
280
+ identity, owned page/network context and initial navigation, or
281
+ the extension handshake.
282
+ `None` resolves to the
272
283
  `AI_DEV_BROWSER_STARTUP_TIMEOUT` env var, else 30s — which
273
284
  covers cold-start on slow Windows machines (fresh profile init
274
285
  + Defender scan + I/O contention from the user's main Chrome).
@@ -506,23 +517,23 @@ def browser_start(
506
517
  from typing import Any
507
518
 
508
519
  launch_options: dict[str, Any] = {"chrome_path": chrome_path} if chrome_path else {}
520
+ launch_overrides = {"--no-startup-window": "", **(override_default_args or {})}
509
521
  if display is not None:
510
522
  launch_options["display"] = display
511
523
  for attempt in range(max_attempts):
512
524
  process = launch_chrome(
513
525
  port=port,
514
526
  headless=headless_resolved,
515
- start_url=start_url,
527
+ start_url="",
516
528
  user_data_dir=str(cdp_data_dir),
517
529
  extra_args=extra_args,
518
- override_default_args=override_default_args,
530
+ override_default_args=launch_overrides,
519
531
  silent_stderr=silent_stderr,
520
532
  stealth=stealth,
521
533
  **launch_options,
522
534
  )
523
535
 
524
- # Browser identity can precede the first page on a loaded host.
525
- # Wait for both read-only handshakes before reporting a usable session.
536
+ # Establish this process's browser identity before creating its owned page.
526
537
  bound = False
527
538
  ready_guid = None
528
539
  ready_target = None
@@ -532,10 +543,8 @@ def browser_start(
532
543
  if is_port_in_use(port=port):
533
544
  ready_guid = _query_chrome_guid(port)
534
545
  if ready_guid:
535
- ready_target = _query_initial_page(port)
536
- if ready_target:
537
- bound = True
538
- break
546
+ bound = True
547
+ break
539
548
  if process.poll() is not None:
540
549
  stderr = process.stderr.read() if process.stderr else ""
541
550
  if not stderr:
@@ -558,7 +567,7 @@ def browser_start(
558
567
  last_error = {
559
568
  "error": (
560
569
  f"Chrome started (PID {orphan_pid}) but port {port} not "
561
- f"ready with a CDP browser identity and initial page after {startup_timeout}s — process killed to "
570
+ f"ready with a CDP browser identity after {startup_timeout}s — process killed to "
562
571
  f"release profile lockfile. Retry with startup_timeout=<larger> "
563
572
  f"if your environment is slow (Windows + main Chrome running, "
564
573
  f"first-time profile init, AV scanning, etc.)."
@@ -584,6 +593,60 @@ def browser_start(
584
593
  continue
585
594
  return last_error
586
595
 
596
+ ready_target = None
597
+ start_options["headless"] = headless_resolved
598
+ spawned_args = getattr(process, "args", None)
599
+ if isinstance(spawned_args, (list, tuple)) and spawned_args:
600
+ # Preserve the executable launched when macOS reports a code-sign clone.
601
+ start_options["chrome_path"] = str(spawned_args[0])
602
+ try:
603
+ ready_target = _run_coro(
604
+ _create_startup_page(
605
+ port,
606
+ ready_guid,
607
+ "about:blank",
608
+ max(0.0, startup_timeout - (time.monotonic() - wait_started)),
609
+ )
610
+ )
611
+ _run_coro(
612
+ _prepare_startup_page(
613
+ port,
614
+ ready_target,
615
+ start_url,
616
+ max(0.0, startup_timeout - (time.monotonic() - wait_started)),
617
+ )
618
+ )
619
+ except Exception as error:
620
+ # A lost creation or navigation reply is ambiguous. Retain Chrome and
621
+ # the known target so reconnect can inspect the outcome.
622
+ registry.register_instance(
623
+ port=port,
624
+ guid=ready_guid,
625
+ workspace=os.getcwd(),
626
+ pid=process.pid,
627
+ user_data_dir=str(cdp_data_dir),
628
+ )
629
+ state = {
630
+ "transport": "cdp",
631
+ "port": port,
632
+ "pid": process.pid,
633
+ "browser_closed": False,
634
+ "user_data_dir": str(cdp_data_dir),
635
+ "headless": headless_resolved,
636
+ }
637
+ if ready_target:
638
+ state = _describe_cdp(
639
+ {**state, "automation_target_id": ready_target},
640
+ start_options=start_options,
641
+ guid=ready_guid,
642
+ )
643
+ return _session.failure(
644
+ "transient",
645
+ f"Chrome's initial page could not be confirmed: {str(error) or type(error).__name__}. "
646
+ "Reconnect and inspect its pages before continuing.",
647
+ **state,
648
+ )
649
+
587
650
  # Record this instance so workspace/profile discovery can find it WITHOUT
588
651
  # reading Chrome's command line over CDP (which needs --enable-automation,
589
652
  # off under stealth). Keyed later by the browser GUID from /json/version so
@@ -664,21 +727,96 @@ def browser_start(
664
727
  # the locale applied while navigator.language was unchanged). Surface
665
728
  # the requested value too when Chrome normalized it away.
666
729
  requested = identity["locale"]
667
- effective = _run_coro(_read_navigator_language(port)) or requested
730
+ effective = (
731
+ _run_coro(_read_navigator_language(port, ready_target)) or requested
732
+ )
668
733
  result["locale"] = effective
669
734
  if effective != requested:
670
735
  result["locale_requested"] = requested
671
736
  if egress_ip:
672
737
  result["egress_ip"] = egress_ip
673
- start_options["headless"] = headless_resolved
674
- spawned_args = getattr(process, "args", None)
675
- if isinstance(spawned_args, (list, tuple)) and spawned_args:
676
- # psutil can observe a temporary macOS code-sign clone. Restart the
677
- # executable we actually launched; retain the observed path for PID checks.
678
- start_options["chrome_path"] = str(spawned_args[0])
679
738
  return _describe_cdp(result, start_options=start_options, guid=ready_guid)
680
739
 
681
740
 
741
+ async def _create_startup_page(port: int, guid: str, url: str, timeout: float) -> str:
742
+ """Create the one startup target on the verified browser, without replay."""
743
+ from ai_dev_browser.cdp import target
744
+ from ._transport import CDPConnection
745
+
746
+ connection = CDPConnection(f"ws://127.0.0.1:{port}/devtools/browser/{guid}")
747
+
748
+ async def create():
749
+ await connection.connect()
750
+ return str(await connection.send(target.create_target(url), timeout=timeout))
751
+
752
+ try:
753
+ return await asyncio.wait_for(create(), timeout=timeout)
754
+ finally:
755
+ await connection.disconnect()
756
+
757
+
758
+ async def _prepare_startup_page(
759
+ port: int, target_id: str, url: str, timeout: float
760
+ ) -> None:
761
+ """Prepare the blank owned page, then issue the requested navigation once.
762
+
763
+ Chrome's temporary empty document can already report readyState=complete.
764
+ Its navigation entry has no name and no DOMContentLoaded timestamp.
765
+ """
766
+ from ai_dev_browser.cdp import network, page, runtime
767
+ from ._transport import CDPConnection, ProtocolException
768
+
769
+ connection = CDPConnection(f"ws://127.0.0.1:{port}/devtools/page/{target_id}")
770
+
771
+ async def wait_document(previous_loader=None):
772
+ while True:
773
+ try:
774
+ frame = (await connection.send(page.get_frame_tree())).frame
775
+ if previous_loader is not None and frame.loader_id == previous_loader:
776
+ await asyncio.sleep(0.01)
777
+ continue
778
+ result, exception = await connection.send(
779
+ runtime.evaluate(
780
+ "performance.getEntriesByType('navigation').some("
781
+ "n => n.name && n.domContentLoadedEventEnd > 0)",
782
+ return_by_value=True,
783
+ )
784
+ )
785
+ if not exception and result.value is True:
786
+ return
787
+ except ProtocolException as error:
788
+ # The initial navigation can replace the temporary JS context.
789
+ if "Execution context was destroyed" not in str(error):
790
+ raise
791
+ await asyncio.sleep(0.01)
792
+
793
+ async def prepare():
794
+ await connection.connect()
795
+ await connection.send(page.enable())
796
+ # A parsed blank page can precede Chrome's profile NetworkContext.
797
+ # This origin-scoped CookieManager RPC waits for that context without
798
+ # making an HTTP request. Discard its result; no cookies are modified.
799
+ await asyncio.gather(
800
+ wait_document(),
801
+ connection.send(network.get_cookies(urls=[f"http://127.0.0.1:{port}/"])),
802
+ )
803
+ if url != "about:blank":
804
+ previous_loader = (
805
+ await connection.send(page.get_frame_tree())
806
+ ).frame.loader_id
807
+ _, loader, error, *_ = await connection.send(page.navigate(url))
808
+ if error:
809
+ raise ProtocolException(
810
+ f"Navigation failed: {error}", method="Page.navigate"
811
+ )
812
+ await wait_document(previous_loader if loader else None)
813
+
814
+ try:
815
+ await asyncio.wait_for(prepare(), timeout=timeout)
816
+ finally:
817
+ await connection.disconnect()
818
+
819
+
682
820
  def _query_initial_page(port: int) -> str | None:
683
821
  """A listening browser endpoint does not prove its initial page exists."""
684
822
  import json
@@ -793,7 +931,7 @@ async def browser_connect(
793
931
  headless: bool | str | None = None,
794
932
  timeout: float = 4.0,
795
933
  ) -> dict:
796
- """Use when: attaching to existing CDP or the user's real Chrome/profile via extension; returns connected, session_id and capabilities.
934
+ """Use when: attaching to existing CDP or real Chrome; returns session identity and extension_warning to check before input.
797
935
 
798
936
  {mode_guidance}
799
937
 
@@ -813,6 +951,9 @@ async def browser_connect(
813
951
  `bundled_extension_version`; an unknown version stays null. Updating Python
814
952
  does not reload Chrome's extension. Ordinary extension reload can reset
815
953
  control: inspect the session and original owned page before further input.
954
+ A non-null extension_warning means the runtime version is unknown or differs
955
+ from the bundle: follow its update/reload steps before input. connected=true
956
+ confirms the connection; actual visibility/readiness comes from window_set.
816
957
 
817
958
  Args:
818
959
  transport: "cdp" (default, existing debug Chrome) or "extension"
@@ -830,6 +971,8 @@ async def browser_connect(
830
971
  owner, profile_owner, actual headless (null if unknown), account for
831
972
  extension, port for CDP, tabs and tab_count. Includes version/git_commit
832
973
  and loaded/bundled extension versions with their observed match status.
974
+ extension_warning is null when versions match, otherwise actionable
975
+ compatibility guidance; version matching alone does not prove visibility.
833
976
 
834
977
  Failure:
835
978
  Read error_code and setup_instructions. Missing bridge/extension needs
@@ -1082,6 +1225,8 @@ async def browser_reconnect(session_id: str, timeout: float = 10.0) -> dict:
1082
1225
  closed page state, or repeat a click/submission. The returned identity and
1083
1226
  tabs confirm reconnection; inspect page/server state before continuing an
1084
1227
  action whose previous result was uncertain.
1228
+ Read any extension_warning before input: a reconnected extension may still
1229
+ need an ordinary update/reload and another explicit connection.
1085
1230
 
1086
1231
  Args:
1087
1232
  session_id: Identity from browser_start/browser_connect.
@@ -680,6 +680,10 @@ async def connect_extension(session_id: str | None = None) -> BrowserClient:
680
680
  viewport override, and it follows popups/new tabs your automation opens (an
681
681
  OAuth account-chooser, a magic-link tab) while never touching your own tabs.
682
682
 
683
+ Before initial workflow input, call browser_connect(transport="extension")
684
+ and resolve any extension_warning about the actual loaded version. Bind its
685
+ returned session_id here; an ordinary extension reload can replace the session.
686
+
683
687
  Pair with `get_active_tab(browser, url_contains=...)` to pick the tab, same
684
688
  as CDP mode.
685
689
 
@@ -39,6 +39,7 @@ from urllib.parse import parse_qs, urlsplit
39
39
  import websockets
40
40
 
41
41
  from .extension import EXTENSION_BRIDGE_PORT
42
+ from ._transport import _local_websocket_options
42
43
 
43
44
  logger = logging.getLogger(__name__)
44
45
 
@@ -370,7 +371,8 @@ async def bridge_status(
370
371
  """Query the daemon's own state (`{extension_connected, account}`) — a status
371
372
  frame it answers without relaying to the extension. None if unreachable."""
372
373
  try:
373
- async with websockets.connect(f"ws://127.0.0.1:{port}") as ws:
374
+ url = f"ws://127.0.0.1:{port}"
375
+ async with websockets.connect(url, **_local_websocket_options(url)) as ws:
374
376
  await ws.send(json.dumps({"id": 1, "method": "_bridge.status"}))
375
377
  resp = json.loads(await asyncio.wait_for(ws.recv(), timeout))
376
378
  return resp.get("result")
@@ -383,7 +385,8 @@ async def shutdown_bridge(
383
385
  ) -> bool:
384
386
  """Close this bridge's sockets through its own protocol, never its host PID."""
385
387
  try:
386
- async with websockets.connect(f"ws://127.0.0.1:{port}") as ws:
388
+ url = f"ws://127.0.0.1:{port}"
389
+ async with websockets.connect(url, **_local_websocket_options(url)) as ws:
387
390
  await ws.send(
388
391
  json.dumps(
389
392
  {
@@ -440,7 +443,7 @@ def _launch_page(connection_or_path, request_or_headers):
440
443
  path = connection_or_path if legacy else request_or_headers.path
441
444
  if not re.fullmatch(r"/adb-launch/[a-f0-9]{32}", path):
442
445
  return None
443
- body = "<!doctype html><meta charset=utf-8><title>Connecting AI Dev Browser</title><p>Waiting for the configured browser extension.</p>"
446
+ body = "<!doctype html><meta charset=utf-8><title>AI Dev Browser</title><p>This window is reserved for your browser task.</p>"
444
447
  if legacy:
445
448
  return (
446
449
  HTTPStatus.OK,
@@ -32,12 +32,30 @@ def extension_version_info(loaded_version: str | None) -> dict:
32
32
  bundled = json.loads(
33
33
  (extension_dir() / "manifest.json").read_text(encoding="utf-8")
34
34
  )["version"]
35
+ matches = loaded_version == bundled if loaded_version else None
36
+ warning = None
37
+ if matches is not True:
38
+ observed = (
39
+ f"Loaded extension {loaded_version} differs from SDK bundle {bundled}"
40
+ if loaded_version
41
+ else f"Loaded extension version is unknown (SDK bundle {bundled})"
42
+ )
43
+ warning = (
44
+ f"{observed}; compatibility has not been verified. Before page input, "
45
+ "align the Python package and running extension with an ordinary "
46
+ "update/reload in chrome://extensions in the intended profile. "
47
+ f"This SDK's bundle is {extension_dir()}. "
48
+ "Python updates do not reload Chrome's extension. Then use "
49
+ "browser_connect --transport extension and inspect session/profile, "
50
+ "owned target and real visibility before resuming; reload may replace "
51
+ "the session. Keep Chrome and its existing tabs; do not replay "
52
+ "earlier submissions."
53
+ )
35
54
  return {
36
55
  "extension_version": loaded_version,
37
56
  "bundled_extension_version": bundled,
38
- "extension_version_matches_bundle": loaded_version == bundled
39
- if loaded_version
40
- else None,
57
+ "extension_version_matches_bundle": matches,
58
+ "extension_warning": warning,
41
59
  }
42
60
 
43
61