ai-dev-browser 0.58.2__tar.gz → 0.58.3__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 (364) hide show
  1. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.github/workflows/ci.yml +8 -3
  2. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/PKG-INFO +1 -1
  3. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/ext_bridge.py +1 -1
  4. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/port.py +56 -15
  5. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/background.js +3 -2
  6. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/manifest.json +1 -1
  7. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/page_view.js +2 -2
  8. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser.egg-info/PKG-INFO +1 -1
  9. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser.egg-info/SOURCES.txt +3 -0
  10. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser.egg-info/scm_file_list.json +3 -0
  11. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/docs/remote-views.md +22 -4
  12. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/conftest.py +20 -0
  13. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/extension_ownership_support.py +18 -0
  14. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/live_cleanup.py +5 -2
  15. ai_dev_browser-0.58.3/tests/integration/live_resources.py +58 -0
  16. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/navigation_startup_support.py +52 -1
  17. ai_dev_browser-0.58.3/tests/integration/test_discovery_socket_pressure.py +108 -0
  18. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_remote_selection_live.py +159 -38
  19. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_remote_view.py +3 -1
  20. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/extension_ownership.test.cjs +23 -0
  21. ai_dev_browser-0.58.3/tests/unit/test_discovery_budget.py +47 -0
  22. ai_dev_browser-0.58.3/tests/unit/test_live_cleanup.py +135 -0
  23. ai_dev_browser-0.58.2/tests/unit/test_live_cleanup.py +0 -65
  24. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.dockerignore +0 -0
  25. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.gitattributes +0 -0
  26. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.github/workflows/publish.yml +0 -0
  27. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.gitignore +0 -0
  28. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.gitmodules +0 -0
  29. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.pre-commit-config.yaml +0 -0
  30. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/.secrets.baseline +0 -0
  31. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/LICENSE +0 -0
  32. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/README.md +0 -0
  33. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/__init__.py +0 -0
  34. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/_cli.py +0 -0
  35. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/_version.py +0 -0
  36. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/__init__.py +0 -0
  37. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/accessibility.py +0 -0
  38. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/animation.py +0 -0
  39. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/audits.py +0 -0
  40. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/autofill.py +0 -0
  41. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/background_service.py +0 -0
  42. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/bluetooth_emulation.py +0 -0
  43. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/browser.py +0 -0
  44. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/cache_storage.py +0 -0
  45. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/cast.py +0 -0
  46. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/console.py +0 -0
  47. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/crash_report_context.py +0 -0
  48. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/css.py +0 -0
  49. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/debugger.py +0 -0
  50. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/device_access.py +0 -0
  51. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/device_orientation.py +0 -0
  52. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/dom.py +0 -0
  53. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/dom_debugger.py +0 -0
  54. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/dom_snapshot.py +0 -0
  55. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/dom_storage.py +0 -0
  56. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/emulation.py +0 -0
  57. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/event_breakpoints.py +0 -0
  58. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/extensions.py +0 -0
  59. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/fed_cm.py +0 -0
  60. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/fetch.py +0 -0
  61. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/file_system.py +0 -0
  62. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/headless_experimental.py +0 -0
  63. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/heap_profiler.py +0 -0
  64. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/indexed_db.py +0 -0
  65. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/input_.py +0 -0
  66. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/inspector.py +0 -0
  67. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/io.py +0 -0
  68. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/layer_tree.py +0 -0
  69. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/log.py +0 -0
  70. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/media.py +0 -0
  71. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/memory.py +0 -0
  72. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/network.py +0 -0
  73. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/overlay.py +0 -0
  74. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/page.py +0 -0
  75. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/performance.py +0 -0
  76. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/performance_timeline.py +0 -0
  77. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/preload.py +0 -0
  78. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/profiler.py +0 -0
  79. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/pwa.py +0 -0
  80. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/py.typed +0 -0
  81. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/runtime.py +0 -0
  82. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/schema.py +0 -0
  83. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/security.py +0 -0
  84. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/service_worker.py +0 -0
  85. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/smart_card_emulation.py +0 -0
  86. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/storage.py +0 -0
  87. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/system_info.py +0 -0
  88. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/target.py +0 -0
  89. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/tethering.py +0 -0
  90. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/tracing.py +0 -0
  91. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/util.py +0 -0
  92. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/web_audio.py +0 -0
  93. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/web_authn.py +0 -0
  94. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/cdp/web_mcp.py +0 -0
  95. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/__init__.py +0 -0
  96. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_browser_guidance.py +0 -0
  97. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_case.py +0 -0
  98. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_demo.py +0 -0
  99. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_element.py +0 -0
  100. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_extension_lifecycle.py +0 -0
  101. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_image_cap.py +0 -0
  102. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_js.py +0 -0
  103. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_recorder.py +0 -0
  104. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_ref.py +0 -0
  105. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_session.py +0 -0
  106. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_tab.py +0 -0
  107. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/_transport.py +0 -0
  108. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/ax.py +0 -0
  109. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/browser.py +0 -0
  110. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/cdp.py +0 -0
  111. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/chrome.py +0 -0
  112. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/cleanup.py +0 -0
  113. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/config.py +0 -0
  114. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/connection.py +0 -0
  115. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/cookies.py +0 -0
  116. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/cookies_import.py +0 -0
  117. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/dialog.py +0 -0
  118. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/download.py +0 -0
  119. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/elements.py +0 -0
  120. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/errors.py +0 -0
  121. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/extension.py +0 -0
  122. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/human.py +0 -0
  123. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/identity.py +0 -0
  124. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/login.py +0 -0
  125. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/mouse.py +0 -0
  126. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/navigation.py +0 -0
  127. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/overlays.py +0 -0
  128. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/page.py +0 -0
  129. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/process.py +0 -0
  130. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/recording.py +0 -0
  131. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/registry.py +0 -0
  132. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/snapshot.py +0 -0
  133. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/storage.py +0 -0
  134. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/tabs.py +0 -0
  135. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/text_match.py +0 -0
  136. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/core/window.py +0 -0
  137. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/README.md +0 -0
  138. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/icon128.png +0 -0
  139. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/icon16.png +0 -0
  140. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/icon48.png +0 -0
  141. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/extension/managed-schema.json +0 -0
  142. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/pool/__init__.py +0 -0
  143. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/pool/job.py +0 -0
  144. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/pool/persistence.py +0 -0
  145. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/pool/pool.py +0 -0
  146. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/pool/worker.py +0 -0
  147. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/profile.py +0 -0
  148. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/py.typed +0 -0
  149. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/__init__.py +0 -0
  150. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/cdp.py +0 -0
  151. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/control.py +0 -0
  152. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/existing.py +0 -0
  153. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/files.py +0 -0
  154. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/mcp-tools.json +0 -0
  155. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/page.py +0 -0
  156. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/profile_lease.py +0 -0
  157. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/provision.py +0 -0
  158. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/session.py +0 -0
  159. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/vnc.py +0 -0
  160. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/window.py +0 -0
  161. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/remote/worker.py +0 -0
  162. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/__init__.py +0 -0
  163. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/_generate.py +0 -0
  164. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_cleanup.py +0 -0
  165. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_connect.py +0 -0
  166. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_disconnect.py +0 -0
  167. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_list.py +0 -0
  168. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_reconnect.py +0 -0
  169. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_restart.py +0 -0
  170. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_start.py +0 -0
  171. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/browser_stop.py +0 -0
  172. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/cdp_send.py +0 -0
  173. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/click_by_html_id.py +0 -0
  174. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/click_by_ref.py +0 -0
  175. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/click_by_text.py +0 -0
  176. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/click_by_xpath.py +0 -0
  177. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/click_row_by_text.py +0 -0
  178. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/cookies_extract_live.py +0 -0
  179. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/cookies_extract_offline.py +0 -0
  180. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/cookies_import.py +0 -0
  181. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/cookies_load.py +0 -0
  182. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/cookies_save.py +0 -0
  183. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/dialog_respond.py +0 -0
  184. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/download.py +0 -0
  185. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/download_link.py +0 -0
  186. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/drag_by_ref.py +0 -0
  187. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/find_by_html_id.py +0 -0
  188. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/find_by_text.py +0 -0
  189. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/find_by_xpath.py +0 -0
  190. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/focus_by_ref.py +0 -0
  191. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/highlight_by_ref.py +0 -0
  192. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/hover_by_ref.py +0 -0
  193. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/html_by_ref.py +0 -0
  194. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/js_evaluate.py +0 -0
  195. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/login_interactive.py +0 -0
  196. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/mouse_click.py +0 -0
  197. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/mouse_drag.py +0 -0
  198. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/mouse_move.py +0 -0
  199. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_discover.py +0 -0
  200. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_emulate_focus.py +0 -0
  201. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_goto.py +0 -0
  202. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_html.py +0 -0
  203. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_info.py +0 -0
  204. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_pdf.py +0 -0
  205. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_record_start.py +0 -0
  206. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_record_stop.py +0 -0
  207. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_reload.py +0 -0
  208. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_screenshot.py +0 -0
  209. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_scroll.py +0 -0
  210. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_wait_element.py +0 -0
  211. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_wait_ready.py +0 -0
  212. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/page_wait_url.py +0 -0
  213. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/press_key.py +0 -0
  214. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/screenshot_by_ref.py +0 -0
  215. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/select_by_ref.py +0 -0
  216. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/select_text.py +0 -0
  217. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/storage_get.py +0 -0
  218. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/storage_set.py +0 -0
  219. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/tab_close.py +0 -0
  220. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/tab_list.py +0 -0
  221. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/tab_new.py +0 -0
  222. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/tab_switch.py +0 -0
  223. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/type_by_ref.py +0 -0
  224. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/type_by_text.py +0 -0
  225. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/upload_by_ref.py +0 -0
  226. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser/tools/window_set.py +0 -0
  227. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser.egg-info/dependency_links.txt +0 -0
  228. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser.egg-info/requires.txt +0 -0
  229. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/ai_dev_browser.egg-info/top_level.txt +0 -0
  230. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/deploy/managed-chrome/Dockerfile +0 -0
  231. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/deploy/managed-chrome/configure_extension.py +0 -0
  232. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/docs/BR-real-profile-hidden-input-resolution.md +0 -0
  233. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/docs/navigation-ownership.md +0 -0
  234. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/pyproject.toml +0 -0
  235. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/scripts/build_release_assets.py +0 -0
  236. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/scripts/live_browser_lifecycle_steering.py +0 -0
  237. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/scripts/live_grok_pool_persistence.py +0 -0
  238. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/scripts/live_recording_steering.py +0 -0
  239. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/scripts/sync_cdp.py +0 -0
  240. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/setup.cfg +0 -0
  241. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/__init__.py +0 -0
  242. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/conftest.py +0 -0
  243. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/__init__.py +0 -0
  244. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/browser_lifecycle_acceptance.json +0 -0
  245. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/browser_lifecycle_journeys.py +0 -0
  246. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/browser_lifecycle_support.py +0 -0
  247. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/browser_lifecycle_ui.py +0 -0
  248. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/cli_steering_acceptance.json +0 -0
  249. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/cli_steering_support.py +0 -0
  250. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/extension_window_support.py +0 -0
  251. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/native_window_occluder.py +0 -0
  252. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/pdf_readiness_support.py +0 -0
  253. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/pool-persistence-acceptance.json +0 -0
  254. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/recording_demo_acceptance.json +0 -0
  255. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/recording_demo_support.py +0 -0
  256. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/run_managed_extension.py +0 -0
  257. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_browser_lifecycle.json +0 -0
  258. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_cli_extension.json +0 -0
  259. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_cli_steering.json +0 -0
  260. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_extension_ownership.json +0 -0
  261. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_extension_ownership_native.json +0 -0
  262. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_extension_window.json +0 -0
  263. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_extension_window_previous.json +0 -0
  264. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_navigation_startup.json +0 -0
  265. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_pdf_readiness.json +0 -0
  266. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_public_web.json +0 -0
  267. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_recording.json +0 -0
  268. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_recording_demo.json +0 -0
  269. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/scenarios_workspace.json +0 -0
  270. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_background_processes.py +0 -0
  271. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_control_live.py +0 -0
  272. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_files_live.py +0 -0
  273. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_inventory.py +0 -0
  274. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_lifecycle.py +0 -0
  275. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_session_live.py +0 -0
  276. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_start_retry.py +0 -0
  277. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_browser_worker_live.py +0 -0
  278. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cdp_send_warning.py +0 -0
  279. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_ci_integration.py +0 -0
  280. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cli_extension_workflows.py +0 -0
  281. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cli_steering.py +0 -0
  282. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cli_unicode_help.py +0 -0
  283. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_click_robust.py +0 -0
  284. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_config_resolvers.py +0 -0
  285. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cookies_extract_hermetic.py +0 -0
  286. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cookies_extract_live.py +0 -0
  287. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cookies_extract_windows.py +0 -0
  288. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_cookies_import.py +0 -0
  289. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_dialog_auto.py +0 -0
  290. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_dialog_respond.py +0 -0
  291. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_dom_discovery.py +0 -0
  292. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_download_link.py +0 -0
  293. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_download_link_frame.py +0 -0
  294. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_existing_worker_live.py +0 -0
  295. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_ext_bridge.py +0 -0
  296. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_extension_ownership.py +0 -0
  297. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_extension_ownership_native.py +0 -0
  298. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_extension_window_native.py +0 -0
  299. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_extension_window_previous.py +0 -0
  300. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_find_and_interact_workflows.py +0 -0
  301. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_image_cap.py +0 -0
  302. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_js_contract.py +0 -0
  303. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_js_evaluate_frame.py +0 -0
  304. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_locator_workflows.py +0 -0
  305. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_mouse_click.py +0 -0
  306. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_mouse_drag_move.py +0 -0
  307. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_multi_profile_pool.py +0 -0
  308. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_navigation_startup.py +0 -0
  309. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_page_discover.py +0 -0
  310. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_page_goto.py +0 -0
  311. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_page_pdf.py +0 -0
  312. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_page_reload.py +0 -0
  313. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_page_scroll_verify.py +0 -0
  314. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_page_wait_element.py +0 -0
  315. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_pdf_readiness.py +0 -0
  316. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_pool_model_persistence.py +0 -0
  317. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_port_detection.py +0 -0
  318. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_press_key.py +0 -0
  319. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_proxy_identity.py +0 -0
  320. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_public_web_workflows.py +0 -0
  321. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_recording_demo.py +0 -0
  322. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_recording_workflows.py +0 -0
  323. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_remote_vnc.py +0 -0
  324. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_remote_window_live.py +0 -0
  325. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_screenshot_coords.py +0 -0
  326. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_scroll_embedded_target.py +0 -0
  327. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_select_text.py +0 -0
  328. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_startup_timeout.py +0 -0
  329. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_stealth_defaults.py +0 -0
  330. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_tab_close.py +0 -0
  331. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_tab_url_matching.py +0 -0
  332. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_text_locator.py +0 -0
  333. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_timeout_and_retry.py +0 -0
  334. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_trusted_click.py +0 -0
  335. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_type_verified_fill.py +0 -0
  336. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_viewport.py +0 -0
  337. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_worker_managed_status_live.py +0 -0
  338. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/integration/test_workspace_workflows.py +0 -0
  339. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_browser_control.py +0 -0
  340. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_build_info.py +0 -0
  341. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_case.py +0 -0
  342. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cdp_coerce.py +0 -0
  343. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cdp_session_scoped.py +0 -0
  344. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cdp_unbound.py +0 -0
  345. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cli_docstring_args.py +0 -0
  346. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cli_error_envelope.py +0 -0
  347. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cli_param_type.py +0 -0
  348. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_cli_steering_contract.py +0 -0
  349. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_demo_encoding.py +0 -0
  350. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_extension_ownership.py +0 -0
  351. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_find_hint.py +0 -0
  352. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_launch_window_size.py +0 -0
  353. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_lifecycle_handshake_errors.py +0 -0
  354. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_no_acronym_rot.py +0 -0
  355. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_pdf_units.py +0 -0
  356. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_recording.py +0 -0
  357. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_release_version.py +0 -0
  358. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_remote_files.py +0 -0
  359. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_remote_navigation.py +0 -0
  360. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_session_failure_state.py +0 -0
  361. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_tool_registry.py +0 -0
  362. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_window_input_contract.py +0 -0
  363. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/tests/unit/test_worker_lifecycle_status.py +0 -0
  364. {ai_dev_browser-0.58.2 → ai_dev_browser-0.58.3}/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"
@@ -301,11 +301,16 @@ jobs:
301
301
  extension-results/**/installed-extension.png
302
302
  extension-results/**/extension-startup-evidence.json
303
303
  extension-results/**/extension-chrome.log
304
+ extension-results/**/host-resources-*.json
305
+ extension-results/**/navigation-readiness.json
304
306
  extension-results/**/extension-cleanup-stacks.txt
305
307
  native-ownership-results/**
306
308
  native-window-results/**/native-window-evidence.json
307
309
  native-window-results/**/native-window-accepted.png
308
310
  native-window-results/**/extension-chrome.log
311
+ native-window-results/**/host-resources-*.json
312
+ native-window-results/**/extension-cleanup-evidence.json
313
+ native-window-results/**/extension-cleanup-stacks.txt
309
314
  previous-extension-results/**/previous-extension-evidence.json
310
315
 
311
316
  # ─────────────────────────────────────────────────────────────────────
@@ -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.3
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
@@ -440,7 +440,7 @@ def _launch_page(connection_or_path, request_or_headers):
440
440
  path = connection_or_path if legacy else request_or_headers.path
441
441
  if not re.fullmatch(r"/adb-launch/[a-f0-9]{32}", path):
442
442
  return None
443
- body = "<!doctype html><meta charset=utf-8><title>Connecting AI Dev Browser</title><p>Waiting for the configured browser extension.</p>"
443
+ body = "<!doctype html><meta charset=utf-8><title>AI Dev Browser</title><p>This window is reserved for your browser task.</p>"
444
444
  if legacy:
445
445
  return (
446
446
  HTTPStatus.OK,
@@ -1,7 +1,7 @@
1
1
  """Port management for Chrome debugging.
2
2
 
3
- Discovery uses port scanning + CDP (no process enumeration):
4
- 1. Scan port range for listening ports (socket.connect)
3
+ Discovery uses the OS listener table + CDP (no process enumeration):
4
+ 1. Read listening ports, with bounded probes on unsupported hosts
5
5
  2. Hit /json/version to confirm Chrome debug port
6
6
  3. CDP Browser.getBrowserCommandLine() to read workspace tag
7
7
 
@@ -11,6 +11,7 @@ Example:
11
11
  """
12
12
 
13
13
  import ipaddress
14
+ import errno
14
15
  import json
15
16
  import logging
16
17
  import os
@@ -146,7 +147,10 @@ def _query_chrome_version(
146
147
 
147
148
 
148
149
  def _guid_from_version(info: dict) -> str | None:
149
- m = _BROWSER_GUID_RE.search(info.get("webSocketDebuggerUrl", "") or "")
150
+ url = info.get("webSocketDebuggerUrl")
151
+ if not isinstance(url, str):
152
+ return None
153
+ m = _BROWSER_GUID_RE.search(url)
150
154
  return m.group(1) if m else None
151
155
 
152
156
 
@@ -174,9 +178,13 @@ def _resolve_instance(
174
178
  so the CDP cmdline is populated) or by an older build.
175
179
  """
176
180
  info = _query_chrome_version(port, host, timeout)
177
- if info is None:
181
+ if not isinstance(info, dict):
178
182
  return None
179
183
  guid = _guid_from_version(info)
184
+ if not guid:
185
+ # An unrelated local HTTP service can also return JSON at this path.
186
+ # It has no browser identity and must not become a CDP candidate.
187
+ return None
180
188
 
181
189
  from . import registry
182
190
 
@@ -454,32 +462,65 @@ def _fast_listening_check(
454
462
  return False
455
463
 
456
464
 
465
+ def _listening_ports(port_range: tuple[int, int]) -> list[int] | None:
466
+ """Read local IPv4 listeners without opening sockets to every candidate.
467
+
468
+ Some hosts do not expose their socket table to ordinary users. Retain the
469
+ existing probe path there, without changing process privileges. Resource
470
+ exhaustion must propagate instead of triggering more socket allocation.
471
+ """
472
+ import psutil
473
+
474
+ try:
475
+ connections = psutil.net_connections(kind="tcp4")
476
+ except OSError as error:
477
+ if (
478
+ error.errno in (errno.ENOBUFS, errno.ENOMEM, errno.EMFILE, errno.ENFILE)
479
+ or getattr(error, "winerror", None) == 10055
480
+ ):
481
+ raise
482
+ return None
483
+ except (psutil.Error, NotImplementedError):
484
+ return None
485
+ return sorted(
486
+ {
487
+ connection.laddr.port
488
+ for connection in connections
489
+ if connection.status == psutil.CONN_LISTEN
490
+ and connection.laddr
491
+ and connection.laddr.ip in (DEFAULT_DEBUG_HOST, "0.0.0.0")
492
+ and port_range[0] <= connection.laddr.port < port_range[1]
493
+ }
494
+ )
495
+
496
+
457
497
  def _scan_ports_for_chrome(
458
498
  port_range: tuple[int, int],
459
- listening_workers: int = 256,
499
+ listening_workers: int = 16,
460
500
  cdp_workers: int = 16,
461
501
  cdp_timeout: float = 0.5,
462
502
  ) -> list[tuple[int, int | None, str | None]]:
463
503
  """Scan a port range for Chrome debug instances (parallelized).
464
504
 
465
505
  Two stages:
466
- 1. Parallel _fast_listening_check() — filter down to listening ports
467
- (bind-first + tight-timeout IPv4 connect, no IPv6)
506
+ 1. OS listener inventory; bounded IPv4 probes only when unavailable
468
507
  2. Parallel _resolve_instance() — verify each is Chrome (via
469
508
  /json/version) and read its workspace from the instance registry
470
509
  (GUID-matched), falling back to Browser.getBrowserCommandLine
471
510
 
472
- Wide worker counts and short timeouts keep scans of the full OS
473
- ephemeral range (~14-16k ports) under 2s.
511
+ Listener inventory avoids hundreds of probe threads and empty TCP
512
+ connections. The fallback keeps a bounded worker count.
474
513
  """
475
514
  ports = list(range(port_range[0], port_range[1]))
476
515
 
477
- with ThreadPoolExecutor(max_workers=listening_workers) as pool:
478
- listening = [
479
- p
480
- for p, live in pool.map(lambda p: (p, _fast_listening_check(p)), ports)
481
- if live
482
- ]
516
+ listening = _listening_ports(port_range)
517
+ if listening is None:
518
+ with ThreadPoolExecutor(max_workers=listening_workers) as pool:
519
+ listening = [
520
+ p
521
+ for p, live in pool.map(lambda p: (p, _fast_listening_check(p)), ports)
522
+ if live
523
+ ]
483
524
 
484
525
  if not listening:
485
526
  return []
@@ -243,7 +243,6 @@ async function ensureDedicatedTabSelected() {
243
243
  if (marker) {
244
244
  launchToken = launchMarker(marker);
245
245
  await chrome.storage.session.set({ adbLaunchToken: launchToken });
246
- await chrome.tabs.update(marker.id, { url: "about:blank" });
247
246
  }
248
247
  mainTabId = t.id;
249
248
  claim(t.id);
@@ -300,7 +299,9 @@ function adoptLaunch(tab) {
300
299
  await chrome.storage.session.set({ adbLaunchToken: token });
301
300
  claim(tab.id, null, "launch");
302
301
  await attachTab(tab.id);
303
- await chrome.tabs.update(tab.id, { url: "about:blank" });
302
+ // The nonce page already belongs to this launch. Keep it until the caller
303
+ // navigates: an extra startup navigation can invalidate Chrome's debugger
304
+ // target after the ownership handshake has announced readiness.
304
305
  if (old != null && old !== tab.id && autoTabs.has(old)) {
305
306
  const previous = await chrome.tabs.get(old).catch(() => null);
306
307
  if (previous && previous.url === "about:blank") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "AI Dev Browser — bridge",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "storage": { "managed_schema": "managed-schema.json" },
6
6
  "description": "Lets ai-dev-browser drive THIS Chrome (your real profile) via chrome.debugger over a local bridge. Opt-in; only active while ai-dev-browser is connected.",
7
7
  "minimum_chrome_version": "116",
@@ -1,6 +1,6 @@
1
1
  /** A reusable pixel surface. The host owns authentication and control policy. */
2
2
  export class PageSurface {
3
- constructor(image, send, { drag = false } = {}) {
3
+ constructor(image, send, { drag = false, touchDrag = false } = {}) {
4
4
  this.image = image;
5
5
  this.send = send;
6
6
  this.width = 0;
@@ -15,7 +15,7 @@ export class PageSurface {
15
15
  const point = this.point(event);
16
16
  if (!point) return;
17
17
  this.pointer = { id: event.pointerId, lastY: event.clientY, startY: event.clientY, startX: event.clientX,
18
- moved: false, drag: drag && event.pointerType !== 'touch', points: [point] };
18
+ moved: false, drag: drag && (event.pointerType !== 'touch' || touchDrag), points: [point] };
19
19
  image.setPointerCapture(event.pointerId);
20
20
  image.focus({ preventScroll: true });
21
21
  });
@@ -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.3
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
@@ -238,6 +238,7 @@ tests/integration/conftest.py
238
238
  tests/integration/extension_ownership_support.py
239
239
  tests/integration/extension_window_support.py
240
240
  tests/integration/live_cleanup.py
241
+ tests/integration/live_resources.py
241
242
  tests/integration/native_window_occluder.py
242
243
  tests/integration/navigation_startup_support.py
243
244
  tests/integration/pdf_readiness_support.py
@@ -279,6 +280,7 @@ tests/integration/test_cookies_extract_windows.py
279
280
  tests/integration/test_cookies_import.py
280
281
  tests/integration/test_dialog_auto.py
281
282
  tests/integration/test_dialog_respond.py
283
+ tests/integration/test_discovery_socket_pressure.py
282
284
  tests/integration/test_dom_discovery.py
283
285
  tests/integration/test_download_link.py
284
286
  tests/integration/test_download_link_frame.py
@@ -341,6 +343,7 @@ tests/unit/test_cli_error_envelope.py
341
343
  tests/unit/test_cli_param_type.py
342
344
  tests/unit/test_cli_steering_contract.py
343
345
  tests/unit/test_demo_encoding.py
346
+ tests/unit/test_discovery_budget.py
344
347
  tests/unit/test_extension_ownership.py
345
348
  tests/unit/test_find_hint.py
346
349
  tests/unit/test_launch_window_size.py
@@ -233,6 +233,7 @@
233
233
  "tests/integration/extension_ownership_support.py",
234
234
  "tests/integration/extension_window_support.py",
235
235
  "tests/integration/live_cleanup.py",
236
+ "tests/integration/live_resources.py",
236
237
  "tests/integration/native_window_occluder.py",
237
238
  "tests/integration/navigation_startup_support.py",
238
239
  "tests/integration/pdf_readiness_support.py",
@@ -274,6 +275,7 @@
274
275
  "tests/integration/test_cookies_import.py",
275
276
  "tests/integration/test_dialog_auto.py",
276
277
  "tests/integration/test_dialog_respond.py",
278
+ "tests/integration/test_discovery_socket_pressure.py",
277
279
  "tests/integration/test_dom_discovery.py",
278
280
  "tests/integration/test_download_link.py",
279
281
  "tests/integration/test_download_link_frame.py",
@@ -336,6 +338,7 @@
336
338
  "tests/unit/test_cli_param_type.py",
337
339
  "tests/unit/test_cli_steering_contract.py",
338
340
  "tests/unit/test_demo_encoding.py",
341
+ "tests/unit/test_discovery_budget.py",
339
342
  "tests/unit/test_extension_ownership.py",
340
343
  "tests/unit/test_find_hint.py",
341
344
  "tests/unit/test_launch_window_size.py",
@@ -53,7 +53,17 @@ dialogs and supported keys. `PageSurface` is a rendering/input adapter, not a
53
53
  complete tab bar, toolbar, WebSocket authorization protocol or React panel.
54
54
  Hosts whose worker advertises `capabilities.dragInput` can opt into mouse/pen
55
55
  selection with `new PageSurface(imageElement, sendAuthorizedInput, {drag: true})`.
56
- Touch gestures continue to scroll. A drag carries at most 64 viewport points in
56
+ Touch gestures scroll by default. A host can offer an explicit "Select text or
57
+ drag" gesture mode with `{drag: true, touchDrag: true}`. A cancelled touch sends
58
+ no partial drag; switching away from a surface must dispose it before accepting
59
+ another gesture. The existing host lease and epoch checks still apply. This is
60
+ an input mode, not an ownership transfer or permission to resume AI. The real
61
+ Chrome touch-emulation journey scrolls out and back in the default mode, then
62
+ selects the exact phrase in drag mode through a separate scaled viewer. It also
63
+ checks cancellation before a completed gesture. Physical iOS/Android acceptance
64
+ remains a separate device requirement.
65
+
66
+ A drag carries at most 64 viewport points in
57
67
  one bounded `input` operation; it is sent on pointer release, so this is not a
58
68
  continuous drag stream. The host's existing input barrier drains the gesture
59
69
  before changing control or targets. The adapter never retries it.
@@ -84,10 +94,11 @@ tool execution and viewer input.
84
94
  ## AI and human control
85
95
 
86
96
  The agent communicates why human input is needed through its native question,
87
- options or normal conversation. The host's existing Take control and Return to
88
- AI actions perform the actual ownership transition. A native answer is only
97
+ options or normal conversation. The host's existing authenticated interrupt
98
+ and continuation actions perform the actual ownership transition. A native answer is only
89
99
  communication unless the host explicitly routes it to that authenticated action
90
- and waits for the tool barrier. Do not build an additional question UI into the
100
+ and waits for the tool barrier. Do not require separate browser business handoff
101
+ buttons or build an additional question UI into the
91
102
  remote display adapter. Credentials are entered in the same browser by the
92
103
  human, not sent through agent chat. The public MCP tool catalog and normal CLI
93
104
  mode help include this guidance; execution barriers remain a host contract.
@@ -197,6 +208,11 @@ refuses arbitrary existing profiles. The host provides the policy/update server
197
208
  and exclusive profile ownership; the SDK does not modify extension preferences
198
209
  or install unpacked code into a user's browser.
199
210
 
211
+ An extension launch keeps its owned loopback handshake page until the caller
212
+ navigates. The handshake does not schedule an additional blank-page navigation:
213
+ that can invalidate Chrome's debugger target after readiness was announced.
214
+ An explicit launch URL still uses the normal caller-requested navigation.
215
+
200
216
  ### Managed Chrome image
201
217
 
202
218
  `deploy/managed-chrome/Dockerfile` is the shared Linux runtime for consuming
@@ -217,6 +233,8 @@ X11 or VNC service by default; hosts provide their authorized session API and
217
233
  start display resources when required. Chrome runs as UID 10001. Persist its
218
234
  host-owned profile separately from the image, enforce one runtime owner, and
219
235
  pin production images by digest after validating their recorded Chrome version.
236
+ Docker hosts must use `--init` or another child-reaping supervisor so exited
237
+ Chrome descendants do not accumulate against the runtime's process limit.
220
238
  The `Managed branded Chrome` CI job uses an isolated fixture signing key and
221
239
  real installed headless extension to exercise the same shared-session workflow
222
240
  as CDP, including trusted input, handoff and stale-target rejection.
@@ -22,6 +22,26 @@ from ai_dev_browser.core.connection import BrowserClient
22
22
  from ai_dev_browser.core.ext_bridge import _Bridge
23
23
  from ai_dev_browser.core.extension import extension_dir
24
24
 
25
+ # Shared fixture visibility must not depend on which test module pytest collects
26
+ # before a module-level pytest_plugins declaration.
27
+ from .test_remote_view import remote_site as remote_site
28
+
29
+
30
+ @pytest.fixture(autouse=True)
31
+ def live_host_resource_receipts(request, tmp_path):
32
+ if "live_extension" not in request.fixturenames and not os.environ.get(
33
+ "AI_DEV_BROWSER_MANAGED_EXTENSION_PROFILE"
34
+ ):
35
+ yield
36
+ return
37
+ from .live_resources import record_host_resources
38
+
39
+ record_host_resources(tmp_path, "before")
40
+ try:
41
+ yield
42
+ finally:
43
+ record_host_resources(tmp_path, "after")
44
+
25
45
 
26
46
  @pytest.fixture
27
47
  def lifecycle(tmp_path):
@@ -97,6 +97,7 @@ async def cli(extension, folder, *arguments, code=0):
97
97
 
98
98
  async def navigation_journey(extension, folder: Path, mode: str):
99
99
  receipts = []
100
+ visits = []
100
101
  url = ""
101
102
 
102
103
  async def receipt(request):
@@ -104,6 +105,7 @@ async def navigation_journey(extension, folder: Path, mode: str):
104
105
  return web.json_response({"accepted": True})
105
106
 
106
107
  async def page(request):
108
+ visits.append(request.path)
107
109
  destination = url.replace("127.0.0.1", "localhost") if mode != "same" else url
108
110
  landing = destination + "/result?private=must-not-log#private-fragment"
109
111
  action = f"location.href={json.dumps(landing)}"
@@ -138,6 +140,22 @@ await fetch('/receipt',{{method:'POST',headers:{{'Content-Type':'application/jso
138
140
  try:
139
141
  browser = await connect(extension)
140
142
  parent = await browser.get(url)
143
+ readiness = await wait_until(
144
+ lambda: parent.evaluate(
145
+ "({protocol:location.protocol,path:location.pathname,"
146
+ "ready:document.readyState,hasSubmit:!!document.querySelector('#submit'),"
147
+ "errorCode:document.querySelector('#error-code')?.textContent||null})"
148
+ ),
149
+ lambda value: value["protocol"] == "chrome-error:"
150
+ or (value["ready"] == "complete" and value["hasSubmit"]),
151
+ )
152
+ (folder / "navigation-readiness.json").write_text(
153
+ json.dumps(
154
+ {"mode": mode, "page": readiness, "fixtureVisits": visits}, indent=2
155
+ ),
156
+ encoding="utf8",
157
+ )
158
+ assert readiness["protocol"] == "http:" and readiness["hasSubmit"], readiness
141
159
  parent_id = str(parent.target_id)
142
160
  sentinel = await worker_call(
143
161
  extension,
@@ -47,6 +47,11 @@ async def close_live_extension(chrome, server, folder, timeout=30):
47
47
  from ai_dev_browser.core.browser import browser_stop
48
48
 
49
49
  try:
50
+ # Let the still-running extension acknowledge WebSocket close frames.
51
+ # Killing Chrome first resets its sockets on Windows and leaves the
52
+ # legacy server waiting for a peer that can no longer finish closing.
53
+ await close_live_server(server, folder)
54
+ finally:
50
55
  if chrome and "port" in chrome:
51
56
  record_phase(folder, "chrome-stop-start", pid=chrome["pid"])
52
57
  try:
@@ -63,5 +68,3 @@ async def close_live_extension(chrome, server, folder, timeout=30):
63
68
  ) from exc
64
69
  record_phase(folder, "chrome-stopped", result=result)
65
70
  assert result.get("stopped") and result.get("count") == 1, result
66
- finally:
67
- await close_live_server(server, folder)
@@ -0,0 +1,58 @@
1
+ """Retain host resource counts, never process arguments or network addresses."""
2
+
3
+ from collections import Counter
4
+ from datetime import datetime, timezone
5
+ import json
6
+ from pathlib import Path
7
+ import sys
8
+
9
+ import psutil
10
+
11
+
12
+ def record_host_resources(folder, phase):
13
+ snapshot = {"phase": phase, "at": datetime.now(timezone.utc).isoformat()}
14
+ try:
15
+ current = psutil.Process()
16
+ memory = psutil.virtual_memory()
17
+ snapshot.update(
18
+ pythonRss=current.memory_info().rss,
19
+ pythonThreads=current.num_threads(),
20
+ hostAvailableMemory=memory.available,
21
+ hostTotalMemory=memory.total,
22
+ tcpStates=dict(
23
+ Counter(
24
+ connection.status for connection in psutil.net_connections("tcp4")
25
+ )
26
+ ),
27
+ )
28
+ chrome = []
29
+ for process in psutil.process_iter(
30
+ ["name", "num_threads", "memory_info"], ad_value=None
31
+ ):
32
+ if "chrome" in (process.info["name"] or "").lower():
33
+ chrome.append(process.info)
34
+ snapshot.update(
35
+ chromeProcessCount=len(chrome),
36
+ chromeThreads=sum(value["num_threads"] or 0 for value in chrome),
37
+ chromeRss=sum(
38
+ value["memory_info"].rss for value in chrome if value["memory_info"]
39
+ ),
40
+ )
41
+ if sys.platform == "linux":
42
+ snapshot["cgroup"] = {
43
+ name: path.read_text().strip()
44
+ for name in (
45
+ "pids.current",
46
+ "pids.max",
47
+ "pids.events",
48
+ "memory.current",
49
+ "memory.max",
50
+ "memory.events",
51
+ )
52
+ if (path := Path("/sys/fs/cgroup") / name).is_file()
53
+ }
54
+ except (psutil.Error, OSError) as error:
55
+ snapshot["auditUnavailable"] = type(error).__name__
56
+ (folder / f"host-resources-{phase}.json").write_text(
57
+ json.dumps(snapshot, indent=2), encoding="utf8"
58
+ )
@@ -9,16 +9,23 @@ import pytest
9
9
  from aiohttp import web
10
10
 
11
11
  from ai_dev_browser.core.browser import browser_start, browser_stop
12
- from tests.integration.test_remote_view import connect_page, navigate_owned_browser
12
+ from tests.integration.test_remote_view import (
13
+ connect_page,
14
+ navigate_owned_browser,
15
+ wait_for,
16
+ )
13
17
 
14
18
 
15
19
  async def startup_race_journey(folder, accepted=True):
16
20
  first_request = asyncio.Event()
17
21
  release_first = asyncio.Event()
18
22
  requests = []
23
+ bootstrap_requests = []
24
+ phases = []
19
25
 
20
26
  async def page(request):
21
27
  requests.append(request.path)
28
+ phases.append({"phase": "target-request", "count": len(requests)})
22
29
  if len(requests) == 1:
23
30
  first_request.set()
24
31
  await release_first.wait()
@@ -31,7 +38,18 @@ async def startup_race_journey(folder, accepted=True):
31
38
  app = web.Application()
32
39
  app.router.add_get("/target", page)
33
40
 
41
+ async def bootstrap(request):
42
+ bootstrap_requests.append(request.path)
43
+ return web.Response(
44
+ text='<title>Race fixture ready</title><main id="bootstrap">Ready</main>',
45
+ content_type="text/html",
46
+ headers={"Cache-Control": "no-store"},
47
+ )
48
+
49
+ app.router.add_get("/bootstrap", bootstrap)
50
+
34
51
  async def redirect(request):
52
+ phases.append({"phase": "competing-redirect"})
35
53
  raise web.HTTPFound("/target" if accepted else "/wrong")
36
54
 
37
55
  app.router.add_get("/redirect", redirect)
@@ -47,6 +65,7 @@ async def startup_race_journey(folder, accepted=True):
47
65
  site = web.TCPSite(runner, "127.0.0.1", 0)
48
66
  await site.start()
49
67
  url = f"http://127.0.0.1:{site._server.sockets[0].getsockname()[1]}/target"
68
+ bootstrap_url = url.replace("/target", "/bootstrap")
50
69
  browser = None
51
70
  navigation = None
52
71
  try:
@@ -64,12 +83,32 @@ async def startup_race_journey(folder, accepted=True):
64
83
  http, browser["port"], browser["automation_target_id"]
65
84
  )
66
85
  try:
86
+ # Chrome for Testing 155 on Windows advertises the CLI startup
87
+ # URL while its document remains about:blank. Prepare this neutral
88
+ # fixture through the existing owned CDP connection exactly once;
89
+ # the cancellation under test is dispatched separately afterwards.
90
+ prepared = await peer.call("Page.navigate", {"url": bootstrap_url})
91
+ assert "errorText" not in prepared, prepared
92
+ await peer.wait_navigation(prepared, timeout=10)
93
+ ready = await wait_for(
94
+ lambda: peer.js(
95
+ "({url:location.href,ready:document.readyState,"
96
+ "marker:document.querySelector('#bootstrap')?.textContent})"
97
+ ),
98
+ lambda value: value["url"] == bootstrap_url
99
+ and value["ready"] in ("interactive", "complete")
100
+ and value["marker"] == "Ready",
101
+ timeout=10,
102
+ )
103
+ assert bootstrap_requests == ["/bootstrap"] and requests == []
104
+ phases.append({"phase": "bootstrap-ready", "observed": ready})
67
105
  navigation = asyncio.create_task(
68
106
  navigate_owned_browser(
69
107
  browser, url, folder, settle_timeout=15 if accepted else 2
70
108
  )
71
109
  )
72
110
  await asyncio.wait_for(first_request.wait(), 10)
111
+ phases.append({"phase": "first-target-held"})
73
112
  # This independent owned driver is the controlled competing
74
113
  # browser navigation; the helper itself still dispatches once.
75
114
  await asyncio.wait_for(
@@ -108,6 +147,18 @@ async def startup_race_journey(folder, accepted=True):
108
147
  await peer.close()
109
148
  finally:
110
149
  release_first.set()
150
+ (folder / "startup-navigation-race-evidence.json").write_text(
151
+ json.dumps(
152
+ {
153
+ "bootstrapRequests": bootstrap_requests,
154
+ "targetRequests": requests,
155
+ "phases": phases,
156
+ "acceptedDestination": accepted,
157
+ },
158
+ indent=2,
159
+ ),
160
+ encoding="utf-8",
161
+ )
111
162
  if browser and "session_id" in browser:
112
163
  stopped = await asyncio.to_thread(
113
164
  browser_stop, session_id=browser["session_id"]