agentenv-framework-protocol 0.1.470__tar.gz → 0.1.472__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 (163) hide show
  1. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/PKG-INFO +37 -3
  2. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/README.md +33 -2
  3. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/pyproject.toml +6 -1
  4. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/browser_cdp.py +5 -0
  5. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/__init__.py +4 -1
  6. agentenv_framework_protocol-0.1.472/src/agentenv_protocol/computer/_x11.py +395 -0
  7. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/app.py +25 -1
  8. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/app_data.py +46 -14
  9. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/app_runtime.py +11 -0
  10. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/apps.py +14 -1
  11. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/dataplane.py +2 -2
  12. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/driver.py +12 -2
  13. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/session.py +20 -3
  14. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/svc.py +8 -4
  15. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website_browser/control.py +39 -0
  16. agentenv_framework_protocol-0.1.472/src/agentenv_protocol/website_browser/data.py +125 -0
  17. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website_browser/environment.py +107 -4
  18. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website_browser/shapes.py +34 -0
  19. agentenv_framework_protocol-0.1.472/tests/test_browser_data.py +313 -0
  20. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_app_data.py +137 -5
  21. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_app_server.py +26 -0
  22. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_conformance.py +17 -9
  23. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_dataplane.py +3 -3
  24. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_svc_forward.py +33 -2
  25. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/.gitignore +0 -0
  26. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/LICENSE +0 -0
  27. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/NOTICE +0 -0
  28. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/THIRD_PARTY_NOTICES.md +0 -0
  29. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/__init__.py +0 -0
  30. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/advanced_agent.py +0 -0
  31. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/basic_agent.py +0 -0
  32. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/custom_extensions_agent.py +0 -0
  33. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/multimodal_agent.py +0 -0
  34. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/reference_model.py +0 -0
  35. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/streaming_agent.py +0 -0
  36. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/.gitignore +0 -0
  37. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/Dockerfile +0 -0
  38. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/Dockerfile.dockerignore +0 -0
  39. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/README.md +0 -0
  40. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/data/seed.json +0 -0
  41. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/env.toml +0 -0
  42. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/site.py +0 -0
  43. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/web/index.html +0 -0
  44. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/web/package-lock.json +0 -0
  45. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/web/package.json +0 -0
  46. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/web/src/main.js +0 -0
  47. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/web/src/style.css +0 -0
  48. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/examples/website/notes/web/vite.config.js +0 -0
  49. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/__init__.py +0 -0
  50. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  51. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  52. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  53. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  54. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  55. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  56. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  57. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  58. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  59. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  60. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  61. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/agent_env_environment.py +0 -0
  62. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/client.py +0 -0
  63. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/_declare.py +0 -0
  64. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/_linux.py +0 -0
  65. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/_load.py +0 -0
  66. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/_macos.py +0 -0
  67. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/_with_fds.py +0 -0
  68. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/card.py +0 -0
  69. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/checks.py +0 -0
  70. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  71. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  72. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
  73. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  74. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
  75. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  76. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/page_fixture.py +0 -0
  77. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  78. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/errors.py +0 -0
  79. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/extensions.py +0 -0
  80. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/frames.py +0 -0
  81. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/keys.py +0 -0
  82. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/launch.py +0 -0
  83. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/manifest.py +0 -0
  84. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/operations.py +0 -0
  85. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/platform.py +0 -0
  86. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/queue.py +0 -0
  87. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/recording.py +0 -0
  88. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/results.py +0 -0
  89. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/run_install_step.py +0 -0
  90. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/runtime.py +0 -0
  91. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/seed.py +0 -0
  92. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/serve.py +0 -0
  93. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/serve_app.py +0 -0
  94. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/settings.py +0 -0
  95. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/skill.py +0 -0
  96. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/spaces.py +0 -0
  97. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/computer/tools.py +0 -0
  98. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/errors.py +0 -0
  99. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/manifest.py +0 -0
  100. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/preflight.py +0 -0
  101. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/tool_access.py +0 -0
  102. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/tool_client.py +0 -0
  103. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/tool_context.py +0 -0
  104. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/transfers.py +0 -0
  105. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/types.py +0 -0
  106. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/__init__.py +0 -0
  107. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/clock.py +0 -0
  108. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/conformance.py +0 -0
  109. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/environment.py +0 -0
  110. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/errors.py +0 -0
  111. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/readiness.py +0 -0
  112. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/routes.py +0 -0
  113. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website/session.py +0 -0
  114. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website_browser/__init__.py +0 -0
  115. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/src/agentenv_protocol/website_browser/devtools.py +0 -0
  116. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/computer_fake.py +0 -0
  117. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/probe_fake.py +0 -0
  118. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_agent.py +0 -0
  119. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_agent_examples.py +0 -0
  120. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_cdp_config.py +0 -0
  121. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_object_transfers.py +0 -0
  122. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_peer_agents.py +0 -0
  123. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_proxy.py +0 -0
  124. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_staging.py +0 -0
  125. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_a2a_trajectory_log.py +0 -0
  126. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_agent_env_environment.py +0 -0
  127. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_browser_cdp.py +0 -0
  128. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_browser_state.py +0 -0
  129. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_client.py +0 -0
  130. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_conformance_checks.py +0 -0
  131. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_conformance_page.py +0 -0
  132. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_conformance_recording.py +0 -0
  133. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_declare.py +0 -0
  134. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_driver.py +0 -0
  135. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_extensions.py +0 -0
  136. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_frames.py +0 -0
  137. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_macos.py +0 -0
  138. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_page.py +0 -0
  139. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_queue.py +0 -0
  140. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_recording.py +0 -0
  141. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_seed.py +0 -0
  142. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_serve.py +0 -0
  143. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_computer_spaces.py +0 -0
  144. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_control_token.py +0 -0
  145. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_data_objects.py +0 -0
  146. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_errors.py +0 -0
  147. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_extension_methods.py +0 -0
  148. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_intake.py +0 -0
  149. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_manifest.py +0 -0
  150. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_preflight.py +0 -0
  151. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_tool_access.py +0 -0
  152. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_tool_annotations.py +0 -0
  153. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_tool_client.py +0 -0
  154. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_tool_context.py +0 -0
  155. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_tool_context_fastmcp.py +0 -0
  156. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_tool_context_injection.py +0 -0
  157. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_website_api.py +0 -0
  158. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_website_browser.py +0 -0
  159. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_website_conformance.py +0 -0
  160. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_website_environment.py +0 -0
  161. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_website_mcp.py +0 -0
  162. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/tests/test_website_notes_example.py +0 -0
  163. {agentenv_framework_protocol-0.1.470 → agentenv_framework_protocol-0.1.472}/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.470
3
+ Version: 0.1.472
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
@@ -58,6 +58,9 @@ Requires-Dist: websockets>=13; extra == 'dev'
58
58
  Provides-Extra: macos
59
59
  Requires-Dist: pyobjc-framework-quartz>=11; (sys_platform == 'darwin') and extra == 'macos'
60
60
  Requires-Dist: pyobjc-framework-screencapturekit>=11; (sys_platform == 'darwin') and extra == 'macos'
61
+ Provides-Extra: x11
62
+ Requires-Dist: mss<11,>=9; extra == 'x11'
63
+ Requires-Dist: python-xlib<0.34,>=0.33; extra == 'x11'
61
64
  Description-Content-Type: text/markdown
62
65
 
63
66
  # agentenv-framework-protocol
@@ -891,13 +894,19 @@ in the session (`agentenv_protocol.computer.session`) is Linux's.
891
894
 
892
895
  The server serves the app's data plane and its skill. With `paths` alone, `data/add` writes the files
893
896
  it's given under them and refuses any other path, or one a link leads out of them; `data/get`
894
- returns the files there, a file tree; and `data/reset` puts them back as the server found them when
897
+ returns the files there, a file tree, copied with every process of the app paused (SIGSTOP) so they
898
+ are of one moment, and sent from the copy once it runs again; and `data/reset` puts them back as the server found them when
895
899
  it first started on this boot, which is the app as installed. Hooks serve what they mark instead.
896
900
  A hook's `data/add` parts come as they were sent, inline or as a read grant, and
897
901
  `part_bytes(part)` returns a part's bytes either way. Either way the base class takes data calls and
898
902
  launches one at a time, runs a load and a reset with the app stopped and relaunches it after,
899
903
  follows a failed load with a reset, and fails a call that runs past `data_timeout_s` (300 s); a sync
900
- hook runs in a thread, which the reset and relaunch after it wait for. The driver fronts its apps:
904
+ hook runs in a thread, which the reset and relaunch after it wait for. An app whose hooks serve an
905
+ operation on the running app names it in `data_live` (a website browser's load sets cookies over its
906
+ pipe), and the base class doesn't stop it for that one; a reset after such a load that failed still
907
+ stops and relaunches it. `_resets_failed_add()` says whether a failed load is followed by a reset at
908
+ all, always by default; and `data_keeps_paths` has the server keep the app's paths as installed for a
909
+ reset hook of its own, which `app_data.reset(paths)` puts back, as the default reset does. The driver fronts its apps:
901
910
  its card lists each one's card as a child environment, rooted at `/svc/<app>/`, where it forwards
902
911
  requests to the app's server, taking the control token:
903
912
 
@@ -915,6 +924,11 @@ agents reach this way without the control token: the driver reads them from the
915
924
  (`computer-app/v1`'s `agent_routes`) and lets a call under one through, while the app checks it
916
925
  itself. A path with a `.` or `..` segment, escaped or not, never counts as under one.
917
926
 
927
+ Once an agent acts in the computer, by any tool call on the driver's `/mcp` or a call on a route an
928
+ app opens to agents, the driver writes `.agentenv/agent-acted` in each app's folder, and
929
+ `agent_acted()` reads it from the app's own: a fresh computer starts without it, and an app that resets
930
+ removes its own with `forget_agent_acted()`. agentenv's own calls, with the control token, don't count.
931
+
918
932
  `MailApp.declaration()` returns what the class declares, an `AppDeclaration`, which a `computer_app`
919
933
  env stores (`browser: true` for a website browser), and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
920
934
  JSON, or a driver's `DriverDeclaration`, without the class's own dependencies installed.
@@ -1016,6 +1030,26 @@ are the profile's template. The base class adds:
1016
1030
  under the `write_namespace` grant when there is one, else inline, as `data/get` returns files. Inline, the whole
1017
1031
  file is read into memory and sent base64 in one answer, so a large one belongs under a grant.
1018
1032
 
1033
+ The browser's data plane is a file tree, every file by its path under the session user's home:
1034
+ - `data/get` returns its state as `browser-state.json` (`BrowserState`: `cookies` and `local_storage`
1035
+ as above, `tabs`, `closed_tabs`, `downloads` and `clock`, `real` with its `now`); its profile's files,
1036
+ each database with its journal or log, copied in one pass with the browser paused once its state is
1037
+ read, so the files are of one moment, without caches (`Cache`, `Code Cache`,
1038
+ `GPUCache`, the shader and Dawn caches, `CacheStorage`, `ScriptCache`, `Crashpad`) or locks
1039
+ (`Singleton*`); and its finished downloads.
1040
+ - `data/add` applies such a state while the browser runs, from `browser-state.json` or a data part of
1041
+ its shape (`StateToLoad`: `cookies`, `local_storage` and `tabs`; what it records of the past is left),
1042
+ after files written by their path: profile files restore the profile to exactly them, with the
1043
+ browser stopped and relaunched first, and files under `~/Downloads` and `~/Uploads` land there; any
1044
+ other path is refused before anything is applied. A load's `tabs` open in place of the open ones,
1045
+ the active one in front; with none, a load that changes cookies or local storage sends the start tabs
1046
+ back to their `start_urls`, so a page loaded before it shows it. Once an agent has acted, a load
1047
+ can't change the profile or the tabs, the start tabs stay as they are, and a load that fails isn't
1048
+ reset: its error says what it had applied.
1049
+ - `data/reset` ends every CDP address, puts the profile back as installed, empties `~/Downloads` and
1050
+ `~/Uploads`, starts its record over and forgets that an agent acted; its data plane relaunches it at
1051
+ its start tabs.
1052
+
1019
1053
  The record's entries are the models in `agentenv_protocol.website_browser` (`OpenTab`, with the tab that opened
1020
1054
  it as `opener`; `ClosedTab`; `Download`; `Dialog`; `LaunchEntry`; `BrowserRecord`), every field present and null
1021
1055
  until it's known.
@@ -829,13 +829,19 @@ in the session (`agentenv_protocol.computer.session`) is Linux's.
829
829
 
830
830
  The server serves the app's data plane and its skill. With `paths` alone, `data/add` writes the files
831
831
  it's given under them and refuses any other path, or one a link leads out of them; `data/get`
832
- returns the files there, a file tree; and `data/reset` puts them back as the server found them when
832
+ returns the files there, a file tree, copied with every process of the app paused (SIGSTOP) so they
833
+ are of one moment, and sent from the copy once it runs again; and `data/reset` puts them back as the server found them when
833
834
  it first started on this boot, which is the app as installed. Hooks serve what they mark instead.
834
835
  A hook's `data/add` parts come as they were sent, inline or as a read grant, and
835
836
  `part_bytes(part)` returns a part's bytes either way. Either way the base class takes data calls and
836
837
  launches one at a time, runs a load and a reset with the app stopped and relaunches it after,
837
838
  follows a failed load with a reset, and fails a call that runs past `data_timeout_s` (300 s); a sync
838
- hook runs in a thread, which the reset and relaunch after it wait for. The driver fronts its apps:
839
+ hook runs in a thread, which the reset and relaunch after it wait for. An app whose hooks serve an
840
+ operation on the running app names it in `data_live` (a website browser's load sets cookies over its
841
+ pipe), and the base class doesn't stop it for that one; a reset after such a load that failed still
842
+ stops and relaunches it. `_resets_failed_add()` says whether a failed load is followed by a reset at
843
+ all, always by default; and `data_keeps_paths` has the server keep the app's paths as installed for a
844
+ reset hook of its own, which `app_data.reset(paths)` puts back, as the default reset does. The driver fronts its apps:
839
845
  its card lists each one's card as a child environment, rooted at `/svc/<app>/`, where it forwards
840
846
  requests to the app's server, taking the control token:
841
847
 
@@ -853,6 +859,11 @@ agents reach this way without the control token: the driver reads them from the
853
859
  (`computer-app/v1`'s `agent_routes`) and lets a call under one through, while the app checks it
854
860
  itself. A path with a `.` or `..` segment, escaped or not, never counts as under one.
855
861
 
862
+ Once an agent acts in the computer, by any tool call on the driver's `/mcp` or a call on a route an
863
+ app opens to agents, the driver writes `.agentenv/agent-acted` in each app's folder, and
864
+ `agent_acted()` reads it from the app's own: a fresh computer starts without it, and an app that resets
865
+ removes its own with `forget_agent_acted()`. agentenv's own calls, with the control token, don't count.
866
+
856
867
  `MailApp.declaration()` returns what the class declares, an `AppDeclaration`, which a `computer_app`
857
868
  env stores (`browser: true` for a website browser), and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
858
869
  JSON, or a driver's `DriverDeclaration`, without the class's own dependencies installed.
@@ -954,6 +965,26 @@ are the profile's template. The base class adds:
954
965
  under the `write_namespace` grant when there is one, else inline, as `data/get` returns files. Inline, the whole
955
966
  file is read into memory and sent base64 in one answer, so a large one belongs under a grant.
956
967
 
968
+ The browser's data plane is a file tree, every file by its path under the session user's home:
969
+ - `data/get` returns its state as `browser-state.json` (`BrowserState`: `cookies` and `local_storage`
970
+ as above, `tabs`, `closed_tabs`, `downloads` and `clock`, `real` with its `now`); its profile's files,
971
+ each database with its journal or log, copied in one pass with the browser paused once its state is
972
+ read, so the files are of one moment, without caches (`Cache`, `Code Cache`,
973
+ `GPUCache`, the shader and Dawn caches, `CacheStorage`, `ScriptCache`, `Crashpad`) or locks
974
+ (`Singleton*`); and its finished downloads.
975
+ - `data/add` applies such a state while the browser runs, from `browser-state.json` or a data part of
976
+ its shape (`StateToLoad`: `cookies`, `local_storage` and `tabs`; what it records of the past is left),
977
+ after files written by their path: profile files restore the profile to exactly them, with the
978
+ browser stopped and relaunched first, and files under `~/Downloads` and `~/Uploads` land there; any
979
+ other path is refused before anything is applied. A load's `tabs` open in place of the open ones,
980
+ the active one in front; with none, a load that changes cookies or local storage sends the start tabs
981
+ back to their `start_urls`, so a page loaded before it shows it. Once an agent has acted, a load
982
+ can't change the profile or the tabs, the start tabs stay as they are, and a load that fails isn't
983
+ reset: its error says what it had applied.
984
+ - `data/reset` ends every CDP address, puts the profile back as installed, empties `~/Downloads` and
985
+ `~/Uploads`, starts its record over and forgets that an agent acted; its data plane relaunches it at
986
+ its start tabs.
987
+
957
988
  The record's entries are the models in `agentenv_protocol.website_browser` (`OpenTab`, with the tab that opened
958
989
  it as `opener`; `ClosedTab`; `Download`; `Dialog`; `LaunchEntry`; `BrowserRecord`), every field present and null
959
990
  until it's known.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.470"
3
+ version = "0.1.472"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -56,6 +56,11 @@ macos = [
56
56
  "pyobjc-framework-Quartz>=11; sys_platform == 'darwin'",
57
57
  "pyobjc-framework-ScreenCaptureKit>=11; sys_platform == 'darwin'",
58
58
  ]
59
+ # A Linux driver on X11Driver: mss captures the X display and XTEST sends its input, through python-xlib.
60
+ x11 = [
61
+ "mss>=9,<11",
62
+ "python-xlib>=0.33,<0.34",
63
+ ]
59
64
  dev = [
60
65
  "a2a-sdk[http-server]>=0.3.26,<0.4",
61
66
  "python-multipart>=0.0.13",
@@ -111,6 +111,11 @@ class Tokens:
111
111
  self._save()
112
112
  return self._minted[user]
113
113
 
114
+ def revoke_all(self) -> None:
115
+ """Every address minted so far ended, as a reset ends them."""
116
+ self._minted, self._known = {}, {}
117
+ self._save()
118
+
114
119
  def address(self, token: str) -> Optional[Address]:
115
120
  """The address ``token`` was minted for, or None for one that wasn't."""
116
121
  known = self._known.get(_hash(token))
@@ -98,16 +98,18 @@ if TYPE_CHECKING:
98
98
  )
99
99
  from .driver import DEFAULT_IDENTITY, ComputerDriverEnvironment, DriverDeclaration, computer_driver
100
100
  from ._macos import MacOSDriver
101
+ from ._x11 import X11Driver
101
102
  from .recording import RecordedFrame, RecordedSegment, RecordHints, Recording, frames_in_thread
102
103
 
103
104
  # The names whose modules need the computer extra (mcp, av and Pillow), by module; MacOSDriver's, the macos extra too,
104
- # on first use of the Mac.
105
+ # on first use of the Mac, and X11Driver's, the x11 extra.
105
106
  _ON_USE = {
106
107
  "driver": ("ComputerDriverEnvironment", "computer_driver", "DEFAULT_IDENTITY", "DriverDeclaration"),
107
108
  "card": ("COMPUTER", "COMPUTER_READY", "COMPUTER_DISPLAY", "COMPUTER_SPACES", "COMPUTER_POINTER",
108
109
  "COMPUTER_KEYBOARD", "COMPUTER_PAGE", "COMPUTER_EXEC", "COMPUTER_RECORDING"),
109
110
  "recording": ("Recording", "RecordHints", "RecordedFrame", "RecordedSegment", "frames_in_thread"),
110
111
  "_macos": ("MacOSDriver",),
112
+ "_x11": ("X11Driver",),
111
113
  }
112
114
 
113
115
 
@@ -120,6 +122,7 @@ def __getattr__(name: str) -> Any:
120
122
 
121
123
  __all__ = [
122
124
  "ComputerDriverEnvironment", "computer_driver", "DEFAULT_IDENTITY", "DriverDeclaration", "MacOSDriver",
125
+ "X11Driver",
123
126
  "ComputerAppEnvironment", "computer_app", "install", "linux", "install_step", "ready_check", "Linux", "Service",
124
127
  "Launch", "Ready", "Packages", "AptSource", "Download", "RunScript",
125
128
  "AppDeclaration", "AppOs",
@@ -0,0 +1,395 @@
1
+ """computer/v1 on an X11 desktop on Linux, for the reference driver and for a driver whose software serves tools of its
2
+ own, as agent-desktop's does: mss captures the screen and XTEST, through python-xlib, sends the input. X calls run off
3
+ the event loop, one at a time, on a connection checked before each call and reopened when it drops, so the driver
4
+ outlives a restarted X server, and input that lets go of a button returns once the server has let go of it. It records
5
+ the display through FFmpeg's ``x11grab``, by PyAV. A driver subclasses ``X11Driver``, declares itself with
6
+ ``computer_driver``, and serves its own tools beside these. Needs the ``x11`` extra: mss and python-xlib."""
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import contextlib
11
+ import math
12
+ import os
13
+ import threading
14
+ import time
15
+ from collections import OrderedDict
16
+ from datetime import datetime, timezone
17
+ from typing import Any, AsyncIterator, Callable, Iterator
18
+
19
+ import av
20
+ import mss
21
+ from Xlib import X, XK
22
+ from Xlib import display as xdisplay
23
+ from Xlib.error import ConnectionClosedError, DisplayConnectionError
24
+ from Xlib.ext import xtest
25
+
26
+ from .driver import ComputerDriverEnvironment
27
+ from .errors import DeliveryFailed, DeviceUnavailable, Unsupported
28
+ from .operations import (Display, Image, ReadyCheck, capture, click, display, drag, key_down, key_up, pointer_down,
29
+ pointer_move, pointer_position, pointer_up, press, record, scroll, type_text)
30
+ from .platform import Platform
31
+ from .recording import RecordedFrame, RecordHints, Recording, frames_in_thread
32
+ from .settings import ComputerSettings
33
+
34
+ BUTTONS = {"left": 1, "middle": 2, "right": 3, "back": 8, "forward": 9}
35
+ #: The pointer state's bit for each button that has one.
36
+ BUTTON_MASKS = {"left": X.Button1Mask, "middle": X.Button2Mask, "right": X.Button3Mask}
37
+ SCROLL = {"up": 4, "down": 5, "left": 6, "right": 7}
38
+ #: W3C key values and their X keysym names; any other single character is typed as its own keysym.
39
+ KEYSYMS = {
40
+ "Enter": "Return", "Tab": "Tab", "Escape": "Escape", "Backspace": "BackSpace", "Delete": "Delete",
41
+ "ArrowUp": "Up", "ArrowDown": "Down", "ArrowLeft": "Left", "ArrowRight": "Right", "Home": "Home", "End": "End",
42
+ "PageUp": "Prior", "PageDown": "Next", "Insert": "Insert", "Control": "Control_L", "Shift": "Shift_L",
43
+ "Alt": "Alt_L", "Meta": "Super_L", "Super": "Super_L", "CapsLock": "Caps_Lock", "ContextMenu": "Menu",
44
+ "PrintScreen": "Print", "Pause": "Pause", " ": "space", **{f"F{n}": f"F{n}" for n in range(1, 25)},
45
+ }
46
+ TYPED = {"\n": "Return", "\t": "Tab"}
47
+ #: The pause between the presses of a double or triple click, in seconds.
48
+ CLICK_GAP_S = 0.04
49
+ #: How long a button's release may take to reach the app, and how often the driver looks.
50
+ RELEASE_WAIT_S = 5.0
51
+ RELEASE_POLL_S = 0.005
52
+ #: How far apart a drag's pointer moves are, in pixels.
53
+ DRAG_STEP_PX = 20
54
+ DROPPED = (ConnectionClosedError, DisplayConnectionError, OSError)
55
+
56
+
57
+ class X11Driver(ComputerDriverEnvironment):
58
+ """The computer/v1 operations, the pointer and keyboard groups among them, on an X11 desktop."""
59
+
60
+ buttons = ("left", "right", "middle", "back", "forward")
61
+ max_click_count = 3
62
+ max_drag_points = 50
63
+ recording = Recording(max_fps=30)
64
+
65
+ def __init__(self, settings: ComputerSettings | None = None, platform: Platform | None = None) -> None:
66
+ super().__init__(settings, platform)
67
+ self._x: _Connection | None = None
68
+ self._x_lock = threading.Lock()
69
+ #: The keycodes each key held by @key_down pressed, which @key_up releases.
70
+ self._held: dict[str, list[int]] = {}
71
+
72
+ @display
73
+ async def screen(self) -> Display:
74
+ def read(c):
75
+ s = c.d.screen()
76
+ return Display(s.width_in_pixels, s.height_in_pixels, 1.0)
77
+ return await self._read(read)
78
+
79
+ @capture
80
+ async def grab(self) -> Image:
81
+ return await asyncio.to_thread(_grab)
82
+
83
+ @record
84
+ async def stream(self, hints: RecordHints) -> AsyncIterator[RecordedFrame]:
85
+ """The display through FFmpeg's ``x11grab``. Its frames carry the wall clock, so each one's time is when it
86
+ was captured."""
87
+ size = await self.screen()
88
+ options = {"framerate": str(hints.fps), "video_size": f"{size.width}x{size.height}",
89
+ "draw_mouse": "1" if hints.cursor else "0"}
90
+
91
+ def frames() -> Iterator[RecordedFrame]:
92
+ with av.open(os.environ.get("DISPLAY", ":0"), format="x11grab", options=options) as source:
93
+ for frame in source.decode(video=0):
94
+ yield RecordedFrame(frame, datetime.fromtimestamp(frame.time, timezone.utc))
95
+
96
+ async for frame in frames_in_thread(frames):
97
+ yield frame
98
+
99
+ async def ready_checks(self) -> list[ReadyCheck]:
100
+ try:
101
+ has = await self._read(lambda c: c.d.has_extension("XTEST"))
102
+ except DeviceUnavailable as e:
103
+ return [ReadyCheck("xtest", False, e.message)]
104
+ return [ReadyCheck("xtest", bool(has), "XTEST available" if has else "XTEST missing")]
105
+
106
+ @click
107
+ async def click_at(self, x, y, button, count, modifiers) -> None:
108
+ syms = [_keysym(k) for k in modifiers]
109
+
110
+ def act(c):
111
+ held = c.hold(syms)
112
+ c.move(x, y)
113
+ for i in range(count):
114
+ c.button(button, True)
115
+ c.button(button, False)
116
+ if i + 1 < count:
117
+ c.d.sync()
118
+ time.sleep(CLICK_GAP_S)
119
+ c.released(button)
120
+ c.release(reversed(held))
121
+ await self._send(act)
122
+
123
+ @drag
124
+ async def drag_along(self, path, button, duration_ms, modifiers) -> None:
125
+ syms = [_keysym(k) for k in modifiers]
126
+ steps = _steps(path)
127
+ pause = duration_ms / 1000 / len(steps)
128
+ held: list[int] = []
129
+
130
+ def start(c):
131
+ held.extend(c.hold(syms))
132
+ c.move(*path[0])
133
+ c.button(button, True)
134
+
135
+ def finish(c):
136
+ c.button(button, False)
137
+ c.released(button)
138
+ c.release(reversed(held))
139
+
140
+ try:
141
+ await self._send(start)
142
+ for px, py in steps:
143
+ await self._send(lambda c, px=px, py=py: c.move(px, py))
144
+ await asyncio.sleep(pause)
145
+ except BaseException:
146
+ with contextlib.suppress(Exception):
147
+ await self._send(finish)
148
+ raise
149
+ await self._send(finish)
150
+
151
+ @scroll
152
+ async def scroll_at(self, x, y, direction, amount, modifiers) -> None:
153
+ syms = [_keysym(k) for k in modifiers]
154
+
155
+ def act(c):
156
+ held = c.hold(syms)
157
+ c.move(x, y)
158
+ for _ in range(amount):
159
+ xtest.fake_input(c.d, X.ButtonPress, SCROLL[direction])
160
+ xtest.fake_input(c.d, X.ButtonRelease, SCROLL[direction])
161
+ c.release(reversed(held))
162
+ await self._send(act)
163
+
164
+ @pointer_move
165
+ async def move(self, x, y) -> None:
166
+ await self._send(lambda c: c.move(x, y))
167
+
168
+ @pointer_down
169
+ async def down(self, button) -> None:
170
+ await self._send(lambda c: c.button(button, True))
171
+
172
+ @pointer_up
173
+ async def up(self, button) -> None:
174
+ def act(c):
175
+ c.button(button, False)
176
+ c.released(button)
177
+ await self._send(act)
178
+
179
+ @pointer_position
180
+ async def where(self) -> tuple[int, int]:
181
+ def read(c):
182
+ p = c.d.screen().root.query_pointer()
183
+ return p.root_x, p.root_y
184
+ return await self._read(read)
185
+
186
+ @type_text
187
+ async def type_in(self, text) -> None:
188
+ syms = [XK.string_to_keysym(TYPED[ch]) if ch in TYPED else _keysym(ch) for ch in text]
189
+ await self._read(lambda c: c.check(syms))
190
+ for sym in syms:
191
+ hold = self.settings.tap_hold_ms / 1000
192
+ await self._send(lambda c, sym=sym: c.tap(sym, hold))
193
+ await asyncio.sleep(self.settings.type_delay_ms / 1000)
194
+
195
+ @press
196
+ async def press_keys(self, keys) -> None:
197
+ syms = [_keysym(k) for k in keys]
198
+ hold = self.settings.tap_hold_ms / 1000
199
+
200
+ def act(c):
201
+ pressed = c.hold(syms)
202
+ c.d.sync()
203
+ time.sleep(hold)
204
+ c.release(reversed(pressed))
205
+ await self._send(act)
206
+
207
+ @key_down
208
+ async def keys_down(self, keys) -> None:
209
+ syms = [_keysym(k) for k in keys]
210
+
211
+ def act(c):
212
+ for key, pressed in zip(keys, c.hold_each(syms)):
213
+ self._held.setdefault(key, []).extend(pressed)
214
+ await self._send(act)
215
+
216
+ @key_up
217
+ async def keys_up(self, keys) -> None:
218
+ def act(c):
219
+ for key in keys:
220
+ c.release(reversed(self._held.pop(key, None) or c.codes(_keysym(key))[-1:]))
221
+ await self._send(act)
222
+
223
+ async def _read(self, fn: Callable[[_Connection], Any]) -> Any:
224
+ return await asyncio.to_thread(self._x_call, fn, False)
225
+
226
+ async def _send(self, fn: Callable[[_Connection], Any]) -> Any:
227
+ return await asyncio.to_thread(self._x_call, fn, True)
228
+
229
+ def _x_call(self, fn: Callable[[_Connection], Any], sends_input: bool) -> Any:
230
+ """``fn`` on a live connection. When the connection drops, a read is retried once on a new one; input is not
231
+ sent again, since some of it may have arrived."""
232
+ with self._x_lock:
233
+ for attempt in (1, 2):
234
+ c = self._connected()
235
+ try:
236
+ result = fn(c)
237
+ c.d.sync()
238
+ return result
239
+ except DROPPED as e:
240
+ self._x = None
241
+ if sends_input:
242
+ raise DeliveryFailed(f"the X connection dropped while sending input: {type(e).__name__}") from None
243
+ if attempt == 2:
244
+ raise DeviceUnavailable(f"the X server is unavailable: {type(e).__name__}") from None
245
+
246
+ def _connected(self) -> _Connection:
247
+ """The connection, opened or reopened as needed and checked with a round trip before anything is sent."""
248
+ for attempt in (1, 2):
249
+ try:
250
+ if self._x is None:
251
+ self._x = _Connection()
252
+ self._held.clear()
253
+ self._x.d.sync()
254
+ return self._x
255
+ except DROPPED as e:
256
+ self._x = None
257
+ if attempt == 2:
258
+ raise DeviceUnavailable(f"the X server is unavailable: {type(e).__name__}") from None
259
+
260
+
261
+ class _Connection:
262
+ """One connection to the X server, with XTEST input on it. A character the keymap lacks is typed through a spare
263
+ keycode mapped to it, kept for that character and reused; once all are taken, the least recently used one that
264
+ no key holds down is mapped again."""
265
+
266
+ def __init__(self) -> None:
267
+ self.d = xdisplay.Display()
268
+ first = self.d.display.info.min_keycode
269
+ mapping = self.d.get_keyboard_mapping(first, self.d.display.info.max_keycode - first + 1)
270
+ self.width = len(mapping[0])
271
+ self.spare = [first + i for i, syms in enumerate(mapping) if not any(syms)]
272
+ self.mapped: OrderedDict[int, int] = OrderedDict()
273
+ self.shift = self.d.keysym_to_keycode(XK.string_to_keysym("Shift_L"))
274
+
275
+ def move(self, x, y) -> None:
276
+ xtest.fake_input(self.d, X.MotionNotify, x=int(x), y=int(y))
277
+
278
+ def button(self, button: str, down: bool) -> None:
279
+ xtest.fake_input(self.d, X.ButtonPress if down else X.ButtonRelease, BUTTONS[button])
280
+
281
+ def released(self, button: str) -> None:
282
+ """Wait, up to RELEASE_WAIT_S, until the server has let go of ``button``. A window manager's passive grab holds
283
+ a click on a window until it replays it to the app, while keys reach the app at once, so a key sent before then
284
+ overtakes the click, and so do the next click's moves."""
285
+ deadline = time.monotonic() + RELEASE_WAIT_S
286
+ while self.d.screen().root.query_pointer().mask & BUTTON_MASKS.get(button, 0) and time.monotonic() < deadline:
287
+ time.sleep(RELEASE_POLL_S)
288
+
289
+ def hold(self, keysyms: list[int]) -> list[int]:
290
+ """``hold_each``'s keycodes, in the order they were pressed."""
291
+ return [code for pressed in self.hold_each(keysyms) for code in pressed]
292
+
293
+ def hold_each(self, keysyms: list[int]) -> list[list[int]]:
294
+ """Press what the keysyms need that isn't down already, Shift first where a character needs it; for each
295
+ keysym, the keycodes this pressed, so the caller releases only those. Every keycode is found before the first
296
+ press, so keysyms that can't all be held at once are refused with nothing sent."""
297
+ down = self._down()
298
+ planned: list[list[int]] = []
299
+ for keysym in keysyms:
300
+ planned.append(self.codes(keysym, busy=down | {code for codes in planned for code in codes}))
301
+ pressed = []
302
+ for codes in planned:
303
+ pressed.append([code for code in codes if code not in down])
304
+ for code in pressed[-1]:
305
+ xtest.fake_input(self.d, X.KeyPress, code)
306
+ down.add(code)
307
+ return pressed
308
+
309
+ def release(self, codes) -> None:
310
+ for code in codes:
311
+ xtest.fake_input(self.d, X.KeyRelease, code)
312
+
313
+ def tap(self, keysym: int, hold_s: float) -> None:
314
+ """Type one character: its key pressed and released, with Shift around it when it needs Shift and nothing
315
+ holds Shift already."""
316
+ down = self._down()
317
+ *modifiers, code = self.codes(keysym, busy=down)
318
+ pressed = [m for m in modifiers if m not in down]
319
+ for m in pressed:
320
+ xtest.fake_input(self.d, X.KeyPress, m)
321
+ xtest.fake_input(self.d, X.KeyPress, code)
322
+ self.d.sync()
323
+ time.sleep(hold_s)
324
+ xtest.fake_input(self.d, X.KeyRelease, code)
325
+ self.release(reversed(pressed))
326
+
327
+ def codes(self, keysym: int, busy: set[int] = frozenset()) -> list[int]:
328
+ """The keycodes that make ``keysym``: Shift and the key when it is the key's second level, else one key; a
329
+ keysym off the keymap is mapped to a spare keycode not in ``busy``."""
330
+ codes = self._on_keymap(keysym)
331
+ if codes is not None:
332
+ return codes
333
+ code = self._free(busy)
334
+ self.d.change_keyboard_mapping(code, [(keysym,) * self.width])
335
+ self.d.sync()
336
+ self.mapped[keysym] = code
337
+ return [code]
338
+
339
+ def check(self, keysyms: list[int]) -> None:
340
+ """Refuse, before anything is sent, text with a character off the keymap when no spare keycode is free."""
341
+ if any(self._on_keymap(k) is None for k in keysyms):
342
+ self._free(self._down(), take=False)
343
+
344
+ def _free(self, busy: set[int], take: bool = True) -> int:
345
+ """A spare keycode not in ``busy``, the least recently used mapped one when none is unmapped."""
346
+ if self.spare:
347
+ return self.spare.pop() if take else self.spare[-1]
348
+ for keysym, code in self.mapped.items():
349
+ if code not in busy:
350
+ if take:
351
+ del self.mapped[keysym]
352
+ return code
353
+ raise Unsupported("no spare keycode is free for a character off the X keymap: the server has none, or keys "
354
+ "hold them all down")
355
+
356
+ def _on_keymap(self, keysym: int) -> list[int] | None:
357
+ if keysym in self.mapped:
358
+ self.mapped.move_to_end(keysym)
359
+ return [self.mapped[keysym]]
360
+ code = self.d.keysym_to_keycode(keysym)
361
+ if code and self.d.keycode_to_keysym(code, 0) == keysym:
362
+ return [code]
363
+ if code and self.d.keycode_to_keysym(code, 1) == keysym:
364
+ return [self.shift, code]
365
+ return None
366
+
367
+ def _down(self) -> set[int]:
368
+ return {8 * i + bit for i, byte in enumerate(self.d.query_keymap()) for bit in range(8) if byte >> bit & 1}
369
+
370
+
371
+ def _grab() -> Image:
372
+ try:
373
+ with mss.MSS() as m:
374
+ shot = m.grab(m.monitors[0])
375
+ return Image(shot.width, shot.height, bytes(shot.bgra), "BGRA")
376
+ except Exception as e:
377
+ raise DeviceUnavailable(f"capture failed: {type(e).__name__}: {e}") from None
378
+
379
+
380
+ def _steps(path: list) -> list[tuple[float, float]]:
381
+ """The points a drag moves through after its start, about every DRAG_STEP_PX pixels."""
382
+ steps = []
383
+ for (x0, y0), (x1, y1) in zip(path, path[1:]):
384
+ n = max(2, int(math.hypot(x1 - x0, y1 - y0) / DRAG_STEP_PX))
385
+ steps += [(x0 + (x1 - x0) * k / n, y0 + (y1 - y0) * k / n) for k in range(1, n + 1)]
386
+ return steps
387
+
388
+
389
+ def _keysym(key: str) -> int:
390
+ if key in KEYSYMS:
391
+ return XK.string_to_keysym(KEYSYMS[key])
392
+ if len(key) == 1:
393
+ code = ord(key)
394
+ return code if code < 0x100 else 0x01000000 | code
395
+ raise Unsupported(f"this driver has no X keysym for {key!r}")
@@ -43,6 +43,9 @@ WINDOW_KEYS = frozenset({"class", "instance", "title"})
43
43
  COMPUTER_APP = "urn:agentenv:computer-app/v1"
44
44
  #: Where an app's own server listens, in the app's folder, and where it says why it last failed to start.
45
45
  SOCKET = Path(".agentenv/server.sock")
46
+ #: Written in an app's folder by its computer's driver once an agent acts there, by a tool call or on a route the app
47
+ #: opens to agents: a fresh computer starts without it, and an app that resets removes its own.
48
+ AGENT_ACTED = Path(".agentenv/agent-acted")
46
49
  FAILURE = Path(".agentenv/server.failure")
47
50
  #: The values of an app's build settings its env fixes in its computer's image, in the app's folder.
48
51
  BUILD_SETTINGS = Path(".agentenv/build_settings.json")
@@ -359,6 +362,17 @@ def _named(os_types: Union[str, list[str], None]) -> Optional[list[str]]:
359
362
  return None if os_types is None else [os_types] if isinstance(os_types, str) else list(os_types)
360
363
 
361
364
 
365
+ def agent_acted() -> bool:
366
+ """Whether an agent has acted in this app's computer since the computer started or the app last reset, as its
367
+ driver notes; read from the app's own folder."""
368
+ return AGENT_ACTED.exists()
369
+
370
+
371
+ def forget_agent_acted() -> None:
372
+ """Start over, as an app that has reset: no agent has acted since."""
373
+ AGENT_ACTED.unlink(missing_ok=True)
374
+
375
+
362
376
  class ComputerAppEnvironment(AgentEnvEnvironment):
363
377
  """Subclass it, declare it with ``@computer_app`` and an OS decorator for each OS (``@linux``), and set ``skill`` as
364
378
  a class attribute; a deploy's settings arrive as ``self.settings``, those its env fixes at build as
@@ -383,6 +397,11 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
383
397
  skill: ClassVar[Optional[str]] = None
384
398
  #: How long one of its data calls may take, a hook or its paths' default, before it fails.
385
399
  data_timeout_s: ClassVar[float] = 300.0
400
+ #: The data operations its hooks serve on the running app, which its data plane neither stops it for nor
401
+ #: relaunches it after; and whether its server keeps its paths as installed for a reset hook of its own to put
402
+ #: back (``app_data.reset``), as it does for the default reset.
403
+ data_live: ClassVar[frozenset[str]] = frozenset()
404
+ data_keeps_paths: ClassVar[bool] = False
386
405
  #: Path prefixes of its own server's routes, each ending in ``/``, that agents reach through the driver without the
387
406
  #: computer's control token; the app checks a call to one itself.
388
407
  agent_routes: ClassVar[tuple[str, ...]] = ()
@@ -426,7 +445,8 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
426
445
  launch_running=self._launch_running())
427
446
  self._computer_app_data = AppDataPlane(
428
447
  self._computer_app, {op: getattr(self, name) for op, name in cls._data_hooks.items()}, self.paths,
429
- cls.data_timeout_s)
448
+ cls.data_timeout_s, live=cls.data_live, keeps_paths=cls.data_keeps_paths,
449
+ resets_failed_add=self._resets_failed_add)
430
450
 
431
451
  def _launch_for(self, launch: Launch) -> Launch:
432
452
  """The launch its server uses, from the one declared for its OS, whose ``ready`` timeout it keeps: as
@@ -449,6 +469,10 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
449
469
  """What a launch does while it runs instead of starting a second copy: nothing, so another copy starts."""
450
470
  return None
451
471
 
472
+ def _resets_failed_add(self) -> bool:
473
+ """Whether a load that fails is followed by a reset, so the app isn't left half loaded: always."""
474
+ return True
475
+
452
476
  @classmethod
453
477
  def settings_on(cls, os_type: Optional[str]) -> type[BaseModel]:
454
478
  """Its ``Settings`` on ``os_type``, with the fields its OS adds."""