agentenv-framework-protocol 0.1.479__tar.gz → 0.1.481__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 (164) hide show
  1. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/PKG-INFO +24 -5
  2. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/README.md +23 -4
  3. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/pyproject.toml +1 -1
  4. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/app.py +4 -1
  5. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/app_data.py +7 -2
  6. agentenv_framework_protocol-0.1.481/src/agentenv_protocol/website_browser/__init__.py +25 -0
  7. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website_browser/control.py +164 -21
  8. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website_browser/environment.py +115 -14
  9. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website_browser/shapes.py +34 -1
  10. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_browser_data.py +91 -7
  11. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_app_data.py +31 -0
  12. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_browser.py +216 -19
  13. agentenv_framework_protocol-0.1.479/src/agentenv_protocol/website_browser/__init__.py +0 -23
  14. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/.gitignore +0 -0
  15. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/LICENSE +0 -0
  16. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/NOTICE +0 -0
  17. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/THIRD_PARTY_NOTICES.md +0 -0
  18. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/__init__.py +0 -0
  19. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/advanced_agent.py +0 -0
  20. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/basic_agent.py +0 -0
  21. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/custom_extensions_agent.py +0 -0
  22. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/multimodal_agent.py +0 -0
  23. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/reference_model.py +0 -0
  24. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/streaming_agent.py +0 -0
  25. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/.gitignore +0 -0
  26. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/Dockerfile +0 -0
  27. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/Dockerfile.dockerignore +0 -0
  28. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/README.md +0 -0
  29. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/data/seed.json +0 -0
  30. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/env.toml +0 -0
  31. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/site.py +0 -0
  32. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/web/index.html +0 -0
  33. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/web/package-lock.json +0 -0
  34. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/web/package.json +0 -0
  35. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/web/src/main.js +0 -0
  36. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/web/src/style.css +0 -0
  37. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/examples/website/notes/web/vite.config.js +0 -0
  38. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/__init__.py +0 -0
  39. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  40. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  41. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  42. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  43. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  44. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  45. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  46. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  47. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  48. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  49. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  50. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/agent_env_environment.py +0 -0
  51. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/browser_cdp.py +0 -0
  52. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/client.py +0 -0
  53. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/__init__.py +0 -0
  54. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/_declare.py +0 -0
  55. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/_linux.py +0 -0
  56. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/_load.py +0 -0
  57. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/_macos.py +0 -0
  58. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/_with_fds.py +0 -0
  59. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/_x11.py +0 -0
  60. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/app_runtime.py +0 -0
  61. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/apps.py +0 -0
  62. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/card.py +0 -0
  63. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/checks.py +0 -0
  64. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  65. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  66. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
  67. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  68. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
  69. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  70. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/page_fixture.py +0 -0
  71. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  72. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/dataplane.py +0 -0
  73. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/driver.py +0 -0
  74. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/errors.py +0 -0
  75. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/extensions.py +0 -0
  76. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/frames.py +0 -0
  77. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/keys.py +0 -0
  78. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/launch.py +0 -0
  79. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/manifest.py +0 -0
  80. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/operations.py +0 -0
  81. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/platform.py +0 -0
  82. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/queue.py +0 -0
  83. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/recording.py +0 -0
  84. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/results.py +0 -0
  85. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/run_install_step.py +0 -0
  86. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/runtime.py +0 -0
  87. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/seed.py +0 -0
  88. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/serve.py +0 -0
  89. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/serve_app.py +0 -0
  90. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/session.py +0 -0
  91. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/settings.py +0 -0
  92. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/skill.py +0 -0
  93. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/spaces.py +0 -0
  94. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/svc.py +0 -0
  95. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/computer/tools.py +0 -0
  96. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/errors.py +0 -0
  97. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/manifest.py +0 -0
  98. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/preflight.py +0 -0
  99. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/tool_access.py +0 -0
  100. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/tool_client.py +0 -0
  101. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/tool_context.py +0 -0
  102. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/transfers.py +0 -0
  103. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/types.py +0 -0
  104. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/__init__.py +0 -0
  105. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/clock.py +0 -0
  106. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/conformance.py +0 -0
  107. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/environment.py +0 -0
  108. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/errors.py +0 -0
  109. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/readiness.py +0 -0
  110. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/routes.py +0 -0
  111. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website/session.py +0 -0
  112. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website_browser/data.py +0 -0
  113. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/src/agentenv_protocol/website_browser/devtools.py +0 -0
  114. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/computer_fake.py +0 -0
  115. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/probe_fake.py +0 -0
  116. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_agent.py +0 -0
  117. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_agent_examples.py +0 -0
  118. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_cdp_config.py +0 -0
  119. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_object_transfers.py +0 -0
  120. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_peer_agents.py +0 -0
  121. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_proxy.py +0 -0
  122. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_staging.py +0 -0
  123. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_a2a_trajectory_log.py +0 -0
  124. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_agent_env_environment.py +0 -0
  125. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_browser_cdp.py +0 -0
  126. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_browser_state.py +0 -0
  127. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_client.py +0 -0
  128. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_app_server.py +0 -0
  129. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_conformance.py +0 -0
  130. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_conformance_checks.py +0 -0
  131. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_conformance_page.py +0 -0
  132. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_conformance_recording.py +0 -0
  133. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_dataplane.py +0 -0
  134. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_declare.py +0 -0
  135. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_driver.py +0 -0
  136. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_extensions.py +0 -0
  137. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_frames.py +0 -0
  138. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_macos.py +0 -0
  139. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_page.py +0 -0
  140. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_queue.py +0 -0
  141. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_recording.py +0 -0
  142. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_seed.py +0 -0
  143. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_serve.py +0 -0
  144. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_spaces.py +0 -0
  145. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_computer_svc_forward.py +0 -0
  146. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_control_token.py +0 -0
  147. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_data_objects.py +0 -0
  148. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_errors.py +0 -0
  149. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_extension_methods.py +0 -0
  150. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_intake.py +0 -0
  151. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_manifest.py +0 -0
  152. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_preflight.py +0 -0
  153. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_tool_access.py +0 -0
  154. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_tool_annotations.py +0 -0
  155. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_tool_client.py +0 -0
  156. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_tool_context.py +0 -0
  157. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_tool_context_fastmcp.py +0 -0
  158. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_tool_context_injection.py +0 -0
  159. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_api.py +0 -0
  160. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_conformance.py +0 -0
  161. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_environment.py +0 -0
  162. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_mcp.py +0 -0
  163. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_notes_example.py +0 -0
  164. {agentenv_framework_protocol-0.1.479 → agentenv_framework_protocol-0.1.481}/tests/test_website_readiness.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentenv-framework-protocol
3
- Version: 0.1.479
3
+ Version: 0.1.481
4
4
  Summary: Open data-plane protocol and server SDK for agent environments.
5
5
  Project-URL: Homepage, https://www.agentenvframework.com
6
6
  Project-URL: Documentation, https://www.agentenvframework.com/docs
@@ -945,14 +945,14 @@ under the session user's home (`.config/chromium` by default), whose contents fr
945
945
  are the profile's template. The base class adds:
946
946
 
947
947
  - **Its settings**, `BrowserSettings`, which a subclass's `Settings` extends: `start_urls`, opened in
948
- tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`), headless
949
- the first from its command line and the rest over its pipe, since headless Chromium takes one; `window`,
948
+ tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`); `window`,
950
949
  1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`; `geolocation` and
951
950
  `notifications`, `deny` (the default), which answers a page's prompt for it, or `ask`; and `dialogs`, who
952
951
  answers a page's JavaScript dialogs: `agent` (the default), or agentenv, accepting (`accept`, a `prompt()`
953
952
  with its default text) or dismissing (`dismiss`) each. Either way agentenv leaves a page that asks before
954
953
  it's left (`beforeunload`), so none of its answers blocks a navigation.
955
- - **The flags agentenv owns**, after the subclass's command and before the start tabs: its profile, its
954
+ - **The flags agentenv owns**, after the subclass's command and before `about:blank`, the one tab its command line
955
+ opens: its profile, its
956
956
  HTTP cache under `~/.cache` (`--disk-cache-dir`), `--remote-debugging-pipe`, `--no-first-run`,
957
957
  `--no-default-browser-check`, `--lang` and, when `/dev/shm` holds under 1 GiB (Docker gives 64 MiB),
958
958
  `--disable-dev-shm-usage`, so it keeps its shared memory in files. With
@@ -1011,6 +1011,8 @@ are the profile's template. The base class adds:
1011
1011
  - `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
1012
1012
  alone, it intercepts;
1013
1013
  - `page/state`, the active tab's id, URL and title and the dialog or file chooser open on it.
1014
+ - `sites/set {sites}`, the env's own sites, each `{name, domains}`, a `*.` domain matching one label more, which
1015
+ the deploy hands it before it launches the browser and its server keeps for every launch after.
1014
1016
  - `cookies/get {urls?}`, `cookies/set {cookies}` and `cookies/delete {name?, domain?, path?}`, on its default
1015
1017
  context, each cookie as Playwright's `storageState` has it, CDP's `Network.Cookie` fields (`name`, `value`,
1016
1018
  `domain`, `path`, `expires`, `httpOnly`, `secure`, `sameSite`), so a storage state exported from Playwright loads
@@ -1033,9 +1035,26 @@ are the profile's template. The base class adds:
1033
1035
  under the `write_namespace` grant when there is one, else inline, as `data/get` returns files. Inline, the whole
1034
1036
  file is read into memory and sent base64 in one answer, so a large one belongs under a grant.
1035
1037
 
1038
+ **Start tabs.** At each launch its start tabs open over its pipe, in a computer and alone alike: the first in the
1039
+ blank tab its command line opened, the rest in tabs it opens blank after it, each held to the window's size
1040
+ (headless) before it goes to its URL, so its page sees that size from its first script; then the first is brought
1041
+ to the front. They load at once, and each settles once it has loaded (its load event fired, its document's final
1042
+ status read), once it failed (a network error, or on one of the env's sites, as `sites/set` named them, a 4xx or
1043
+ 5xx status, which settles it at once), or once it has been waited on: every one settles within the browser's
1044
+ ready timeout of its launch, the launch's setup counted against it, a tab on one of the env's sites waited on what's
1045
+ left of it and any other on 30 s at most, so `data/reset`, a load and a deploy, which wait that timeout and a margin,
1046
+ always get its outcome. Its `start_tabs_loaded` ready check passes once all have settled, so `cdp` and a deploy wait
1047
+ for them. `record`'s `start_tabs` keeps each launch's (`StartTabsLaunch`: `at` and its
1048
+ `tabs`, each a `StartTab`: `tab`, `url`, `final_url`, `status`, `error`, `load_ms`, `site` — the env's site's
1049
+ name, null for a live one — and `outcome`, `loaded`, `failed` or `timed_out`), and `data/get`'s state keeps its
1050
+ latest launch's. A deploy of a computer reads them once it's ready, keeps them in its record and fails, naming
1051
+ each, if one on the env's sites didn't load; `data/reset` and a load that opens the start tabs again fail
1052
+ the same way, while a server's restart only records it.
1053
+
1036
1054
  The browser's data plane is a file tree, every file by its path under the session user's home:
1037
1055
  - `data/get` returns its state as `browser-state.json` (`BrowserState`: `cookies` and `local_storage`
1038
- as above, `tabs`, `closed_tabs`, `downloads` and `clock`, `real` with its `now`); its profile's files,
1056
+ as above, `tabs`, `closed_tabs`, `downloads`, `clock`, `real` with its `now`, and `start_tabs`); its
1057
+ profile's files,
1039
1058
  each database with its journal or log, copied in one pass with the browser paused once its state is
1040
1059
  read, so the files are of one moment, without caches (`Cache`, `Code Cache`,
1041
1060
  `GPUCache`, the shader and Dawn caches, `CacheStorage`, `ScriptCache`, `Crashpad`) or locks
@@ -880,14 +880,14 @@ under the session user's home (`.config/chromium` by default), whose contents fr
880
880
  are the profile's template. The base class adds:
881
881
 
882
882
  - **Its settings**, `BrowserSettings`, which a subclass's `Settings` extends: `start_urls`, opened in
883
- tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`), headless
884
- the first from its command line and the rest over its pipe, since headless Chromium takes one; `window`,
883
+ tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`); `window`,
885
884
  1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`; `geolocation` and
886
885
  `notifications`, `deny` (the default), which answers a page's prompt for it, or `ask`; and `dialogs`, who
887
886
  answers a page's JavaScript dialogs: `agent` (the default), or agentenv, accepting (`accept`, a `prompt()`
888
887
  with its default text) or dismissing (`dismiss`) each. Either way agentenv leaves a page that asks before
889
888
  it's left (`beforeunload`), so none of its answers blocks a navigation.
890
- - **The flags agentenv owns**, after the subclass's command and before the start tabs: its profile, its
889
+ - **The flags agentenv owns**, after the subclass's command and before `about:blank`, the one tab its command line
890
+ opens: its profile, its
891
891
  HTTP cache under `~/.cache` (`--disk-cache-dir`), `--remote-debugging-pipe`, `--no-first-run`,
892
892
  `--no-default-browser-check`, `--lang` and, when `/dev/shm` holds under 1 GiB (Docker gives 64 MiB),
893
893
  `--disable-dev-shm-usage`, so it keeps its shared memory in files. With
@@ -946,6 +946,8 @@ are the profile's template. The base class adds:
946
946
  - `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
947
947
  alone, it intercepts;
948
948
  - `page/state`, the active tab's id, URL and title and the dialog or file chooser open on it.
949
+ - `sites/set {sites}`, the env's own sites, each `{name, domains}`, a `*.` domain matching one label more, which
950
+ the deploy hands it before it launches the browser and its server keeps for every launch after.
949
951
  - `cookies/get {urls?}`, `cookies/set {cookies}` and `cookies/delete {name?, domain?, path?}`, on its default
950
952
  context, each cookie as Playwright's `storageState` has it, CDP's `Network.Cookie` fields (`name`, `value`,
951
953
  `domain`, `path`, `expires`, `httpOnly`, `secure`, `sameSite`), so a storage state exported from Playwright loads
@@ -968,9 +970,26 @@ are the profile's template. The base class adds:
968
970
  under the `write_namespace` grant when there is one, else inline, as `data/get` returns files. Inline, the whole
969
971
  file is read into memory and sent base64 in one answer, so a large one belongs under a grant.
970
972
 
973
+ **Start tabs.** At each launch its start tabs open over its pipe, in a computer and alone alike: the first in the
974
+ blank tab its command line opened, the rest in tabs it opens blank after it, each held to the window's size
975
+ (headless) before it goes to its URL, so its page sees that size from its first script; then the first is brought
976
+ to the front. They load at once, and each settles once it has loaded (its load event fired, its document's final
977
+ status read), once it failed (a network error, or on one of the env's sites, as `sites/set` named them, a 4xx or
978
+ 5xx status, which settles it at once), or once it has been waited on: every one settles within the browser's
979
+ ready timeout of its launch, the launch's setup counted against it, a tab on one of the env's sites waited on what's
980
+ left of it and any other on 30 s at most, so `data/reset`, a load and a deploy, which wait that timeout and a margin,
981
+ always get its outcome. Its `start_tabs_loaded` ready check passes once all have settled, so `cdp` and a deploy wait
982
+ for them. `record`'s `start_tabs` keeps each launch's (`StartTabsLaunch`: `at` and its
983
+ `tabs`, each a `StartTab`: `tab`, `url`, `final_url`, `status`, `error`, `load_ms`, `site` — the env's site's
984
+ name, null for a live one — and `outcome`, `loaded`, `failed` or `timed_out`), and `data/get`'s state keeps its
985
+ latest launch's. A deploy of a computer reads them once it's ready, keeps them in its record and fails, naming
986
+ each, if one on the env's sites didn't load; `data/reset` and a load that opens the start tabs again fail
987
+ the same way, while a server's restart only records it.
988
+
971
989
  The browser's data plane is a file tree, every file by its path under the session user's home:
972
990
  - `data/get` returns its state as `browser-state.json` (`BrowserState`: `cookies` and `local_storage`
973
- as above, `tabs`, `closed_tabs`, `downloads` and `clock`, `real` with its `now`); its profile's files,
991
+ as above, `tabs`, `closed_tabs`, `downloads`, `clock`, `real` with its `now`, and `start_tabs`); its
992
+ profile's files,
974
993
  each database with its journal or log, copied in one pass with the browser paused once its state is
975
994
  read, so the files are of one moment, without caches (`Cache`, `Code Cache`,
976
995
  `GPUCache`, the shader and Dawn caches, `CacheStorage`, `ScriptCache`, `Crashpad`) or locks
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.479"
3
+ version = "0.1.481"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -446,7 +446,7 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
446
446
  self._computer_app_data = AppDataPlane(
447
447
  self._computer_app, {op: getattr(self, name) for op, name in cls._data_hooks.items()}, self.paths,
448
448
  cls.data_timeout_s, live=cls.data_live, keeps_paths=cls.data_keeps_paths,
449
- resets_failed_add=self._resets_failed_add)
449
+ resets_failed_add=self._resets_failed_add, relaunched=self._relaunched)
450
450
 
451
451
  def _launch_for(self, launch: Launch) -> Launch:
452
452
  """The launch its server uses, from the one declared for its OS, whose ``ready`` timeout it keeps: as
@@ -473,6 +473,9 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
473
473
  """Whether a load that fails is followed by a reset, so the app isn't left half loaded: always."""
474
474
  return True
475
475
 
476
+ async def _relaunched(self, op: str) -> None:
477
+ """What a data call that stopped the app, ``op``, awaits once it has launched it again: nothing."""
478
+
476
479
  @classmethod
477
480
  def settings_on(cls, os_type: Optional[str]) -> type[BaseModel]:
478
481
  """Its ``Settings`` on ``os_type``, with the fields its OS adds."""
@@ -11,7 +11,7 @@ import inspect
11
11
  import logging
12
12
  import shutil
13
13
  import tempfile
14
- from collections.abc import Callable
14
+ from collections.abc import Awaitable, Callable
15
15
  from pathlib import Path, PurePosixPath
16
16
  from typing import TYPE_CHECKING, Any, Optional
17
17
 
@@ -41,7 +41,8 @@ class AppDataPlane:
41
41
 
42
42
  def __init__(self, runtime: AppRuntime, hooks: dict[str, Callable], paths: list[str], timeout_s: float, *,
43
43
  live: frozenset[str] = frozenset(), keeps_paths: bool = False,
44
- resets_failed_add: Callable[[], bool] = lambda: True) -> None:
44
+ resets_failed_add: Callable[[], bool] = lambda: True,
45
+ relaunched: Optional[Callable[[str], Awaitable[None]]] = None) -> None:
45
46
  self.runtime = runtime
46
47
  self.hooks = hooks
47
48
  self.paths = paths
@@ -49,6 +50,8 @@ class AppDataPlane:
49
50
  #: The operations served on the running app; whether a hook's reset needs the paths as installed; and whether
50
51
  #: a load that fails is followed by a reset now.
51
52
  self.live, self.keeps_paths, self.resets_failed_add = live, keeps_paths, resets_failed_add
53
+ #: What a call that stopped the app awaits once it has launched it again, whose error fails the call.
54
+ self.relaunched = relaunched
52
55
  self.turn = asyncio.Lock()
53
56
  self.changing: set[asyncio.Task] = set()
54
57
 
@@ -99,6 +102,8 @@ class AppDataPlane:
99
102
  finally:
100
103
  if running:
101
104
  await self.runtime.launch_app(_CAUSES[op])
105
+ if running and self.relaunched is not None:
106
+ await self.relaunched(op)
102
107
  except Exception as e:
103
108
  if not answer.done():
104
109
  answer.set_exception(e)
@@ -0,0 +1,25 @@
1
+ """WebsiteBrowserEnvironment: a website browser as a computer app, run inside a computer or alone. A subclass says how
2
+ its build installs and starts; this class adds what agentenv owns, the same in both: its flags after the subclass's
3
+ command, the unattended-use policies, a CDP connection over the browser's debugging pipe, the settings every browser
4
+ takes, a fresh cache and the deploy's prompt answers at every launch, another window for a launch while it runs, its
5
+ skill and ``browser/v1`` on its card. It runs alone, headless, on a computer with no display, a browser computer's,
6
+ whose screen is its page; which site proxy it goes through comes from a variable agentenv sets, never from its
7
+ settings."""
8
+ from .control import (ACTIVE_CHECK_TIMEOUT_S, AGENT, AGENTENV, CAPTURE_TIMEOUT_S, DOWNLOADS, EXIT, FAILED,
9
+ HASH_CHUNK_BYTES, LIVE_START_TAB_TIMEOUT_S, LOADED, PAGE_INPUT, PAGES, PIN_TIMEOUT_S, STOP,
10
+ TIMED_OUT, UNKNOWN, UNNAVIGATED_CAPTURE_TIMEOUT_S, UPLOADS, BrowserControl)
11
+ from .devtools import MAX_MESSAGE_BYTES, PIPE_IN, PIPE_OUT, DevToolsError, DevToolsPipe
12
+ from .environment import (ALONE_BYPASS, BLOCK, BROWSER, COMPUTER_BYPASS, OWNED_FLAGS, POLICIES, READY_POLL_S,
13
+ SHM_MIN_BYTES, SITE_PROXY_ENV, SITES, SKILL, BrowserSettings, WebsiteBrowserEnvironment,
14
+ Window, start_tab_why)
15
+ from .shapes import (BrowserRecord, ClosedTab, Cookie, CookieToSet, Dialog, Download, LaunchEntry, OpenTab,
16
+ OriginStorage, PartitionKey, SiteNames, StartTab, StartTabsLaunch, StorageItem, origin_of)
17
+
18
+ __all__ = ["ACTIVE_CHECK_TIMEOUT_S", "AGENT", "AGENTENV", "ALONE_BYPASS", "BLOCK", "BROWSER", "CAPTURE_TIMEOUT_S",
19
+ "COMPUTER_BYPASS", "DOWNLOADS", "EXIT", "FAILED", "HASH_CHUNK_BYTES", "LIVE_START_TAB_TIMEOUT_S", "LOADED",
20
+ "MAX_MESSAGE_BYTES", "OWNED_FLAGS", "PAGE_INPUT", "PAGES", "PIN_TIMEOUT_S", "PIPE_IN", "PIPE_OUT",
21
+ "POLICIES", "READY_POLL_S", "SHM_MIN_BYTES", "SITE_PROXY_ENV", "SITES", "SKILL", "STOP", "TIMED_OUT",
22
+ "UNKNOWN", "UNNAVIGATED_CAPTURE_TIMEOUT_S", "UPLOADS", "BrowserControl", "BrowserSettings", "DevToolsError",
23
+ "DevToolsPipe", "WebsiteBrowserEnvironment", "Window", "BrowserRecord", "ClosedTab", "Cookie", "CookieToSet",
24
+ "Dialog", "Download", "LaunchEntry", "OpenTab", "OriginStorage", "PartitionKey", "SiteNames", "StartTab",
25
+ "StartTabsLaunch", "StorageItem", "origin_of", "start_tab_why"]
@@ -8,6 +8,7 @@ import hashlib
8
8
  import logging
9
9
  import time
10
10
  from collections.abc import AsyncIterator
11
+ from dataclasses import dataclass, field
11
12
  from datetime import datetime, timezone
12
13
  from pathlib import Path
13
14
  from typing import Any, Awaitable, Callable, Optional
@@ -15,7 +16,7 @@ from urllib.parse import urlsplit
15
16
 
16
17
  from ..computer.errors import DeviceUnavailable, InvalidArgument
17
18
  from .devtools import DevToolsError, DevToolsPipe
18
- from .shapes import (ClosedTab, Cookie, CookieToSet, Dialog, Download, OpenTab, OriginStorage, StorageItem,
19
+ from .shapes import (ClosedTab, Cookie, CookieToSet, Dialog, Download, OpenTab, OriginStorage, StartTab, StorageItem,
19
20
  origin_of)
20
21
 
21
22
  logger = logging.getLogger(__name__)
@@ -29,6 +30,10 @@ ACTIVE_CHECK_TIMEOUT_S = 2.0
29
30
  #: of agentenv's own may take to reach an origin, and how often it's asked meanwhile.
30
31
  PIN_TIMEOUT_S = 10.0
31
32
  ARRIVAL_POLL_S = 0.05
33
+ #: How long a start tab on a site the env doesn't serve may take to load before it's left loading.
34
+ LIVE_START_TAB_TIMEOUT_S = 30.0
35
+ #: How a start tab settled.
36
+ LOADED, FAILED, TIMED_OUT = "loaded", "failed", "timed_out"
32
37
  #: How much of a download each read hashes.
33
38
  HASH_CHUNK_BYTES = 1 << 20
34
39
  #: Who answered a dialog when agentenv didn't.
@@ -80,6 +85,8 @@ class BrowserControl:
80
85
  self._dialog_opened: dict[str, asyncio.Event] = {}
81
86
  self._pinned: dict[str, asyncio.Event] = {}
82
87
  self._tasks: set[asyncio.Future] = set()
88
+ #: Each start tab loading, by its session.
89
+ self._loading: dict[str, _StartLoad] = {}
83
90
  #: Every http and https origin a page or frame it follows loaded since this server started, and the sessions
84
91
  #: of agentenv's own pages whose requests it answers itself.
85
92
  self.origins: set[str] = set()
@@ -108,6 +115,9 @@ class BrowserControl:
108
115
  self._shots.clear()
109
116
  self._dialog_opened.clear()
110
117
  self._pinned.clear()
118
+ for load in self._loading.values():
119
+ load.error = load.error or "the browser ended"
120
+ load.settled.set()
111
121
 
112
122
  async def close_tab(self, target: str) -> None:
113
123
  """Close the tab ``target``, which the record says agentenv closed."""
@@ -138,18 +148,84 @@ class BrowserControl:
138
148
  await self._bring_to_front(active)
139
149
  return target
140
150
 
141
- async def reopen_start_tabs(self, urls: list[str]) -> None:
142
- """The start tabs at their ``urls`` again, in place, the first active: each tab open, in opened order, goes to
143
- its URL, and any the browser lacks opens. A page already loaded doesn't read cookies set after it, so a tab
144
- a load signed in shows it only once it loads again."""
145
- tabs = list(self.tabs)
146
- for index, url in enumerate(urls):
147
- if index < len(tabs):
148
- await self._navigate_tab(tabs[index], url)
149
- else:
150
- tabs.append(await self.open_tab(url))
151
- if tabs:
152
- await self._bring_to_front(tabs[0])
151
+ async def load_start_tabs(self, urls: list[str], site_of: Callable[[str], Optional[str]], own_timeout_s: float,
152
+ live_timeout_s: float = LIVE_START_TAB_TIMEOUT_S) -> list[dict]:
153
+ """Each of ``urls`` in a start tab, in order, the first then active: in the tabs open now, in opened order, and
154
+ in tabs it opens blank after them; each held to the window's size, headless, before it goes to its URL, and
155
+ all loading at once. A tab settles once it has loaded, once it has failed, which on one of the env's own sites,
156
+ as ``site_of`` names them, an error status is too, or once it has been waited on ``own_timeout_s`` on such a
157
+ site, else ``live_timeout_s``. Each tab's outcome, in order, kept though the browser has gone and the first
158
+ can't come to the front. A page already loaded doesn't read cookies set after it, so a tab a load signed in
159
+ shows it only once it loads again."""
160
+ if not urls:
161
+ return []
162
+ targets = list(self.tabs)[:len(urls)] or [await self._first_tab()]
163
+ while len(targets) < len(urls):
164
+ targets.append((await self.devtools.call("Target.createTarget",
165
+ {"url": "about:blank", "background": True}))["targetId"])
166
+ for target in targets:
167
+ await asyncio.wait_for(self._pinned.setdefault(target, asyncio.Event()).wait(), PIN_TIMEOUT_S)
168
+ loading = []
169
+ for target, url in zip(targets, urls):
170
+ site = site_of(url)
171
+ loading.append(await self._start_load(target, url, site))
172
+ outcomes = await asyncio.gather(*(self._settled(target, load, own_timeout_s if load.site is not None
173
+ else live_timeout_s) for target, load in zip(targets, loading)))
174
+ with contextlib.suppress(ConnectionError, DevToolsError):
175
+ await self._bring_to_front(targets[0])
176
+ return outcomes
177
+
178
+ async def _first_tab(self) -> str:
179
+ """The tab a browser just launched opened, once it has one."""
180
+ deadline = asyncio.get_running_loop().time() + PIN_TIMEOUT_S
181
+ while not self.tabs:
182
+ if asyncio.get_running_loop().time() > deadline:
183
+ raise DeviceUnavailable(f"the browser opened no tab within {PIN_TIMEOUT_S:g} s")
184
+ await asyncio.sleep(ARRIVAL_POLL_S)
185
+ return next(iter(self.tabs))
186
+
187
+ async def _start_load(self, target: str, url: str, site: Optional[str]) -> _StartLoad:
188
+ """The tab ``target`` sent to ``url``, its navigation followed by what the browser says of it. The browser
189
+ answers a navigation once it commits, which a page that never answers never does, so its answer is awaited
190
+ apart, for as long as the tab is."""
191
+ session = next((s for s, t in self._sessions.items() if t == target), None)
192
+ if session is None:
193
+ raise DeviceUnavailable(f"the browser's tab {target} has no session to load a start tab in")
194
+ load = self._loading[session] = _StartLoad(session, url, site, asyncio.get_running_loop().time())
195
+ try:
196
+ await self.devtools.call("Network.enable", session_id=session)
197
+ except (ConnectionError, DevToolsError, asyncio.TimeoutError) as e:
198
+ load.error = f"{type(e).__name__}: {e}"
199
+ load.check()
200
+ return load
201
+ load.navigating = asyncio.ensure_future(self.devtools.request("Page.navigate", {"url": url},
202
+ session_id=session))
203
+ load.navigating.add_done_callback(load.answered)
204
+ return load
205
+
206
+ async def _settled(self, target: str, load: _StartLoad, timeout_s: float) -> dict:
207
+ """``load``'s outcome once it settles, or once it has been waited on ``timeout_s``."""
208
+ try:
209
+ await asyncio.wait_for(load.settled.wait(), timeout_s)
210
+ except asyncio.TimeoutError:
211
+ pass
212
+ finally:
213
+ if load.navigating is not None:
214
+ load.navigating.cancel()
215
+ self._loading.pop(load.session, None)
216
+ with contextlib.suppress(ConnectionError, DevToolsError, asyncio.TimeoutError):
217
+ await self.devtools.call("Network.disable", session_id=load.session)
218
+ document = load.current() or load.loader or ""
219
+ status, final_url = load.responses.get(document, (None, None))
220
+ error = load.error or load.failures.get(load.loader or "") or load.failures.get(document)
221
+ outcome = FAILED if error or (load.settled.is_set() and status is not None and status >= 400) else \
222
+ LOADED if load.settled.is_set() else TIMED_OUT
223
+ if final_url is None and outcome == LOADED:
224
+ await self._refresh(target)
225
+ final_url = self.tabs.get(target, {}).get("url")
226
+ return StartTab(tab=target, url=load.url, final_url=final_url, status=status, error=error,
227
+ load_ms=round((asyncio.get_running_loop().time() - load.started) * 1000), site=load.site,
228
+ outcome=outcome).model_dump()
153
229
 
154
230
  async def replace_tabs(self, tabs: list) -> None:
155
231
  """``tabs``, each with its ``url`` and whether it's ``active``, opened in order in place of the tabs open now,
@@ -161,14 +237,6 @@ class BrowserControl:
161
237
  if opened:
162
238
  await self._bring_to_front(next((target for target, tab in zip(opened, tabs) if tab.active), opened[0]))
163
239
 
164
- async def _navigate_tab(self, target: str, url: str) -> None:
165
- session = next((s for s, t in self._sessions.items() if t == target), None)
166
- if session is None:
167
- raise DeviceUnavailable(f"the browser's tab {target} has no session to navigate it")
168
- answer = await self.devtools.call("Page.navigate", {"url": url}, session_id=session)
169
- if error := answer.get("errorText"):
170
- raise InvalidArgument(f"the browser couldn't open {url}: {error}")
171
-
172
240
  def forget(self) -> None:
173
241
  """A record that starts now, as a reset leaves the browser: no tabs closed, downloads or dialogs before it, and
174
242
  no origin seen."""
@@ -521,6 +589,26 @@ class BrowserControl:
521
589
  def _on_Page_frameNavigated(self, params: dict, session: Optional[str]) -> None:
522
590
  if session in self._sessions:
523
591
  self._saw(params.get("frame", {}).get("url", ""))
592
+ frame = params.get("frame", {})
593
+ if (load := self._loading.get(session)) is not None and not frame.get("parentId") and frame.get("loaderId"):
594
+ load.commits.append(frame["loaderId"])
595
+ load.check()
596
+
597
+ def _on_Network_requestWillBeSent(self, params: dict, session: Optional[str]) -> None:
598
+ if (load := self._loading.get(session)) is not None and params.get("type") == "Document":
599
+ load.requests[params["requestId"]] = params.get("loaderId", "")
600
+
601
+ def _on_Network_responseReceived(self, params: dict, session: Optional[str]) -> None:
602
+ if (load := self._loading.get(session)) is not None and params.get("type") == "Document" and \
603
+ params.get("requestId") in load.requests:
604
+ response = params.get("response", {})
605
+ load.responses[load.requests[params["requestId"]]] = (response.get("status"), response.get("url"))
606
+ load.check()
607
+
608
+ def _on_Network_loadingFailed(self, params: dict, session: Optional[str]) -> None:
609
+ if (load := self._loading.get(session)) is not None and (loader := load.requests.get(params.get("requestId"))):
610
+ load.failures[loader] = params.get("errorText") or "the load failed"
611
+ load.check()
524
612
 
525
613
  def _on_Fetch_requestPaused(self, params: dict, session: Optional[str]) -> None:
526
614
  if session in self._serving:
@@ -575,6 +663,9 @@ class BrowserControl:
575
663
  self._choosers.pop(session, None)
576
664
  if (target := self._sessions.get(session)) is not None:
577
665
  self._spawn(self._refresh(target))
666
+ if (load := self._loading.get(session)) is not None and load.commits:
667
+ load.loaded.add(load.commits[-1])
668
+ load.check()
578
669
 
579
670
  def _on_Page_fileChooserOpened(self, params: dict, session: Optional[str]) -> None:
580
671
  if session in self._sessions:
@@ -636,6 +727,58 @@ class BrowserControl:
636
727
  logger.warning(f"the browser's control connection: {type(error).__name__}: {error}")
637
728
 
638
729
 
730
+ @dataclass
731
+ class _StartLoad:
732
+ """What the browser has said of a start tab's navigation, by loader, since it was sent to ``url``: its main frame's
733
+ commits in order, the loads that fired after a commit, and its documents' requests, final responses and failures.
734
+ Its document is the last its main frame committed from its own navigation on, which a page that sends itself
735
+ elsewhere before it loads replaces. It's ``settled`` once its navigation or its document has failed, its document
736
+ has loaded or, on one of the env's ``site``, answered an error."""
737
+ session: str
738
+ url: str
739
+ site: Optional[str]
740
+ started: float
741
+ loader: Optional[str] = None
742
+ error: Optional[str] = None
743
+ commits: list[str] = field(default_factory=list)
744
+ loaded: set[str] = field(default_factory=set)
745
+ requests: dict[str, str] = field(default_factory=dict)
746
+ responses: dict[str, tuple[Optional[int], Optional[str]]] = field(default_factory=dict)
747
+ failures: dict[str, str] = field(default_factory=dict)
748
+ settled: asyncio.Event = field(default_factory=asyncio.Event)
749
+ navigating: Optional[asyncio.Future] = None
750
+
751
+ def answered(self, navigating: asyncio.Future) -> None:
752
+ """The browser's answer to its navigation: its loader, or why it couldn't navigate."""
753
+ if navigating.cancelled():
754
+ return
755
+ if (error := navigating.exception()) is not None:
756
+ self.error = f"{type(error).__name__}: {error}"
757
+ else:
758
+ answer = navigating.result()
759
+ if "error" in answer:
760
+ self.error = str(answer["error"].get("message", answer["error"]))
761
+ else:
762
+ self.error = answer.get("result", {}).get("errorText") or None
763
+ self.loader = answer.get("result", {}).get("loaderId")
764
+ self.check()
765
+
766
+ def current(self) -> Optional[str]:
767
+ """The loader of its document: the last its main frame committed, once its own navigation has; else None."""
768
+ return self.commits[-1] if self.loader is not None and self.loader in self.commits else None
769
+
770
+ def check(self) -> None:
771
+ if self.error or (self.loader is not None and self.loader in self.failures):
772
+ self.settled.set()
773
+ return
774
+ if (document := self.current()) is None:
775
+ return
776
+ status = self.responses.get(document, (None, None))[0]
777
+ if document in self.failures or document in self.loaded or (
778
+ self.site is not None and status is not None and status >= 400):
779
+ self.settled.set()
780
+
781
+
639
782
  def _size_and_sha256(path: Path) -> tuple[int, str]:
640
783
  digest = hashlib.sha256()
641
784
  with path.open("rb") as file: