agentenv-framework-protocol 0.1.490__tar.gz → 0.1.491__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 (174) hide show
  1. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/PKG-INFO +17 -7
  2. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/README.md +16 -6
  3. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/pyproject.toml +1 -1
  4. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/client.py +3 -3
  5. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_session_posix.py +46 -7
  6. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/app.py +2 -1
  7. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/app_runtime.py +6 -6
  8. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/checks.py +18 -10
  9. agentenv_framework_protocol-0.1.491/src/agentenv_protocol/computer/conformance/desk.py +65 -0
  10. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/fixture.py +32 -44
  11. agentenv_framework_protocol-0.1.491/src/agentenv_protocol/computer/conformance/wayland_fixture.py +299 -0
  12. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website_browser/control.py +5 -2
  13. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website_browser/shapes.py +1 -1
  14. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_client.py +34 -0
  15. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_app_server.py +86 -1
  16. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_conformance.py +63 -2
  17. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_website_browser.py +21 -0
  18. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/.gitignore +0 -0
  19. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/LICENSE +0 -0
  20. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/NOTICE +0 -0
  21. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/THIRD_PARTY_NOTICES.md +0 -0
  22. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/__init__.py +0 -0
  23. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/advanced_agent.py +0 -0
  24. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/basic_agent.py +0 -0
  25. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/custom_extensions_agent.py +0 -0
  26. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/multimodal_agent.py +0 -0
  27. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/reference_model.py +0 -0
  28. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/streaming_agent.py +0 -0
  29. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/.gitignore +0 -0
  30. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/Dockerfile +0 -0
  31. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/Dockerfile.dockerignore +0 -0
  32. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/README.md +0 -0
  33. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/data/seed.json +0 -0
  34. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/env.toml +0 -0
  35. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/site.py +0 -0
  36. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/web/index.html +0 -0
  37. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/web/package-lock.json +0 -0
  38. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/web/package.json +0 -0
  39. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/web/src/main.js +0 -0
  40. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/web/src/style.css +0 -0
  41. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/examples/website/notes/web/vite.config.js +0 -0
  42. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/__init__.py +0 -0
  43. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  44. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  45. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  46. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  47. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  48. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  49. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  50. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  51. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  52. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  53. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  54. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/agent_env_environment.py +0 -0
  55. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/browser_cdp.py +0 -0
  56. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/__init__.py +0 -0
  57. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_declare.py +0 -0
  58. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_job.py +0 -0
  59. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_linux.py +0 -0
  60. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_load.py +0 -0
  61. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_macos.py +0 -0
  62. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_reaping.py +0 -0
  63. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_session_windows.py +0 -0
  64. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_with_fds.py +0 -0
  65. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/_x11.py +0 -0
  66. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/app_data.py +0 -0
  67. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/apps.py +0 -0
  68. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/card.py +0 -0
  69. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/checks.py +0 -0
  70. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  71. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  72. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  73. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  74. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/page_fixture.py +0 -0
  75. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  76. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/dataplane.py +0 -0
  77. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/driver.py +0 -0
  78. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/errors.py +0 -0
  79. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/extensions.py +0 -0
  80. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/frames.py +0 -0
  81. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/keys.py +0 -0
  82. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/launch.py +0 -0
  83. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/manifest.py +0 -0
  84. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/operations.py +0 -0
  85. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/platform.py +0 -0
  86. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/prefix.py +0 -0
  87. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/queue.py +0 -0
  88. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/recording.py +0 -0
  89. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/results.py +0 -0
  90. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/run_install_step.py +0 -0
  91. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/runtime.py +0 -0
  92. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/seed.py +0 -0
  93. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/serve.py +0 -0
  94. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/serve_app.py +0 -0
  95. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/served.py +0 -0
  96. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/session.py +0 -0
  97. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/settings.py +0 -0
  98. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/skill.py +0 -0
  99. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/spaces.py +0 -0
  100. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/svc.py +0 -0
  101. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/computer/tools.py +0 -0
  102. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/errors.py +0 -0
  103. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/manifest.py +0 -0
  104. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/preflight.py +0 -0
  105. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/tool_access.py +0 -0
  106. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/tool_client.py +0 -0
  107. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/tool_context.py +0 -0
  108. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/transfers.py +0 -0
  109. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/types.py +0 -0
  110. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/__init__.py +0 -0
  111. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/clock.py +0 -0
  112. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/conformance.py +0 -0
  113. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/environment.py +0 -0
  114. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/errors.py +0 -0
  115. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/readiness.py +0 -0
  116. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/routes.py +0 -0
  117. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website/session.py +0 -0
  118. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website_browser/__init__.py +0 -0
  119. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website_browser/data.py +0 -0
  120. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website_browser/devtools.py +0 -0
  121. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/src/agentenv_protocol/website_browser/environment.py +0 -0
  122. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/computer_fake.py +0 -0
  123. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/probe_fake.py +0 -0
  124. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_agent.py +0 -0
  125. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_agent_examples.py +0 -0
  126. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_cdp_config.py +0 -0
  127. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_object_transfers.py +0 -0
  128. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_peer_agents.py +0 -0
  129. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_proxy.py +0 -0
  130. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_staging.py +0 -0
  131. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_a2a_trajectory_log.py +0 -0
  132. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_agent_env_environment.py +0 -0
  133. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_browser_cdp.py +0 -0
  134. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_browser_data.py +0 -0
  135. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_browser_state.py +0 -0
  136. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_app_data.py +0 -0
  137. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_conformance_checks.py +0 -0
  138. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_conformance_page.py +0 -0
  139. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_conformance_recording.py +0 -0
  140. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_dataplane.py +0 -0
  141. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_declare.py +0 -0
  142. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_driver.py +0 -0
  143. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_extensions.py +0 -0
  144. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_frames.py +0 -0
  145. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_macos.py +0 -0
  146. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_page.py +0 -0
  147. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_queue.py +0 -0
  148. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_recording.py +0 -0
  149. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_seed.py +0 -0
  150. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_serve.py +0 -0
  151. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_served.py +0 -0
  152. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_session_windows.py +0 -0
  153. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_spaces.py +0 -0
  154. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_svc_forward.py +0 -0
  155. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_computer_tool_prefix.py +0 -0
  156. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_control_token.py +0 -0
  157. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_data_objects.py +0 -0
  158. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_errors.py +0 -0
  159. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_extension_methods.py +0 -0
  160. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_intake.py +0 -0
  161. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_manifest.py +0 -0
  162. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_preflight.py +0 -0
  163. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_tool_access.py +0 -0
  164. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_tool_annotations.py +0 -0
  165. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_tool_client.py +0 -0
  166. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_tool_context.py +0 -0
  167. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_tool_context_fastmcp.py +0 -0
  168. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_tool_context_injection.py +0 -0
  169. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_website_api.py +0 -0
  170. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_website_conformance.py +0 -0
  171. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_website_environment.py +0 -0
  172. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_website_mcp.py +0 -0
  173. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/tests/test_website_notes_example.py +0 -0
  174. {agentenv_framework_protocol-0.1.490 → agentenv_framework_protocol-0.1.491}/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.490
3
+ Version: 0.1.491
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
@@ -757,15 +757,21 @@ driver booted with a control token is probed with it: `--token`, by default
757
757
  | Level | Checks | Needs on the computer |
758
758
  |---|---|---|
759
759
  | `basic` | P0 ready and a screenshot; P1 the card and tool schemas; P2 native frames, regions and JPEG; P5 refusals and their codes; X the declared extensions (`computer-display`, `computer-spaces`, `computer-keyboard`, `computer-exec`, `data/get`, per-role tool access, the pointer) | nothing |
760
- | `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held; on a page, X the page tools: a prompt answered, a file uploaded, a reload, navigation, back and forward, and tabs | Linux, `python3` with Tk (on a page, `python3` and `computer-page/v1`), a 1280x800 screen or larger, `computer-exec` and `data/add` |
760
+ | `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held; on a page, X the page tools: a prompt answered, a file uploaded, a reload, navigation, back and forward, and tabs | Linux, `python3` with Tk (on Wayland, with PyGObject and GTK 3 instead; on a page, `python3` and `computer-page/v1`), a 1280x800 screen or larger, `computer-exec` and `data/add` |
761
761
 
762
- At `full` the probe writes the Fixture Desk, a Tk window (`conformance/fixture.py`), into
763
- `~/.agentenv-probe` with `data/add` and starts it with `computer-exec`. It aims at the boxes the
762
+ At `full` the probe writes the Fixture Desk, a Tk window (`conformance/fixture.py`), and its layout
763
+ (`conformance/desk.py`) into `~/.agentenv-probe` with `data/add` and starts it with `computer-exec`. It aims at the boxes the
764
764
  window's widgets report, reads back what reached them, and then removes the fixture. On X11 the
765
765
  window is maximized and kept above other windows, raising itself again when one covers it, so a
766
766
  window an app keeps on top doesn't take the clicks aimed at it; elsewhere it is full screen. When the
767
767
  fixture can't be staged, the `full` checks are unverified rather than failed.
768
768
 
769
+ On a driver whose platform's display is Wayland, the desk is drawn with GTK 3 as a native Wayland client
770
+ (`conformance/wayland_fixture.py`, staged beside `desk.py` in the Tk window's place), since Tk runs
771
+ only under XWayland. It goes full screen, takes its origin from Hyprland's IPC, keeps the clipboard
772
+ through wl-clipboard. There the probe selects a text box's text with GTK's own Ctrl+A rather than
773
+ Tk's Ctrl+/.
774
+
769
775
  On a driver whose card says `screen: "page"`, the desk is an HTML page with the same widgets at the
770
776
  same places (`conformance/page_fixture.py`). The probe starts its server on loopback, which writes
771
777
  what the page reports to the same files, and opens the desk with `computer_tabs` in a tab of its
@@ -852,7 +858,8 @@ class MailApp(ComputerAppEnvironment):
852
858
  it runs on (`platform.detect()`).
853
859
  - **`launch=Launch(...)`** says how it starts in the desktop session: with it (`at="boot"`) or when
854
860
  the agent opens it (`"demand"`). `ready=Ready(...)` says when it's ready, within `timeout_s` of its
855
- launch: a window matching `launch.window` by its class, instance or title, a port answering on
861
+ launch: a window matching `launch.window` by its class, instance or title (on Hyprland, a Wayland
862
+ app ID or an X window's class, its program's file name, and its title), a port answering on
856
863
  loopback, or a command exiting 0. `env` sets variables in the app's environment, over its server's. `@ready_check` marks a method for readiness that needs logic (a
857
864
  sync one runs in a thread), and `@install_step(scope=...)` one a computer runs as it installs the
858
865
  app, after its run scripts, offline and without the class's `__init__`. Each runs on every OS the
@@ -886,13 +893,16 @@ desktop session, and `ready` (GET: an `AppReadiness` of `ready`, `problems`, `la
886
893
  and `timeout_s`), which runs the app's readiness checks then, at once, each within 10 s. A server
887
894
  that can't start, or whose start in the session fails (adding the launcher, say), writes why to
888
895
  `.agentenv/server.failure` in the folder and exits, and the driver reports that in its place. Once
889
- the X display is up, when `DISPLAY` names one, the server adds the app's launcher to the session user's
896
+ the session is up, the X display when `DISPLAY` names one or on Hyprland its Wayland display and its
897
+ IPC, the server adds the app's launcher to the session user's
890
898
  menu, which runs `python -m agentenv_protocol.computer.launch <folder>` to ask the server, so the
891
899
  server starts every copy of the app, and launches a `boot` app, once per boot. An app that runs with
892
900
  no desktop, a website browser alone, gets no launcher. The driver finds each app by the socket in its
893
901
  folder: the computer is ready once every app's server answers and each `boot` app, and each app with
894
902
  no launch, says it's ready; a `demand` app counts once something launches it. What the server does
895
- in the session (`agentenv_protocol.computer.session`) is Linux's.
903
+ in the session (`agentenv_protocol.computer.session`) is Linux's. On Hyprland it lists windows through
904
+ Hyprland's IPC (`hyprctl -j clients`), which sees native Wayland windows and XWayland's alike, where
905
+ X's `xprop` sees only XWayland's.
896
906
 
897
907
  The server serves the app's data plane and its skill. With `paths` alone, `data/add` writes the files
898
908
  it's given under them and refuses any other path, or one a link leads out of them; `data/get`
@@ -692,15 +692,21 @@ driver booted with a control token is probed with it: `--token`, by default
692
692
  | Level | Checks | Needs on the computer |
693
693
  |---|---|---|
694
694
  | `basic` | P0 ready and a screenshot; P1 the card and tool schemas; P2 native frames, regions and JPEG; P5 refusals and their codes; X the declared extensions (`computer-display`, `computer-spaces`, `computer-keyboard`, `computer-exec`, `data/get`, per-role tool access, the pointer) | nothing |
695
- | `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held; on a page, X the page tools: a prompt answered, a file uploaded, a reload, navigation, back and forward, and tabs | Linux, `python3` with Tk (on a page, `python3` and `computer-page/v1`), a 1280x800 screen or larger, `computer-exec` and `data/add` |
695
+ | `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held; on a page, X the page tools: a prompt answered, a file uploaded, a reload, navigation, back and forward, and tabs | Linux, `python3` with Tk (on Wayland, with PyGObject and GTK 3 instead; on a page, `python3` and `computer-page/v1`), a 1280x800 screen or larger, `computer-exec` and `data/add` |
696
696
 
697
- At `full` the probe writes the Fixture Desk, a Tk window (`conformance/fixture.py`), into
698
- `~/.agentenv-probe` with `data/add` and starts it with `computer-exec`. It aims at the boxes the
697
+ At `full` the probe writes the Fixture Desk, a Tk window (`conformance/fixture.py`), and its layout
698
+ (`conformance/desk.py`) into `~/.agentenv-probe` with `data/add` and starts it with `computer-exec`. It aims at the boxes the
699
699
  window's widgets report, reads back what reached them, and then removes the fixture. On X11 the
700
700
  window is maximized and kept above other windows, raising itself again when one covers it, so a
701
701
  window an app keeps on top doesn't take the clicks aimed at it; elsewhere it is full screen. When the
702
702
  fixture can't be staged, the `full` checks are unverified rather than failed.
703
703
 
704
+ On a driver whose platform's display is Wayland, the desk is drawn with GTK 3 as a native Wayland client
705
+ (`conformance/wayland_fixture.py`, staged beside `desk.py` in the Tk window's place), since Tk runs
706
+ only under XWayland. It goes full screen, takes its origin from Hyprland's IPC, keeps the clipboard
707
+ through wl-clipboard. There the probe selects a text box's text with GTK's own Ctrl+A rather than
708
+ Tk's Ctrl+/.
709
+
704
710
  On a driver whose card says `screen: "page"`, the desk is an HTML page with the same widgets at the
705
711
  same places (`conformance/page_fixture.py`). The probe starts its server on loopback, which writes
706
712
  what the page reports to the same files, and opens the desk with `computer_tabs` in a tab of its
@@ -787,7 +793,8 @@ class MailApp(ComputerAppEnvironment):
787
793
  it runs on (`platform.detect()`).
788
794
  - **`launch=Launch(...)`** says how it starts in the desktop session: with it (`at="boot"`) or when
789
795
  the agent opens it (`"demand"`). `ready=Ready(...)` says when it's ready, within `timeout_s` of its
790
- launch: a window matching `launch.window` by its class, instance or title, a port answering on
796
+ launch: a window matching `launch.window` by its class, instance or title (on Hyprland, a Wayland
797
+ app ID or an X window's class, its program's file name, and its title), a port answering on
791
798
  loopback, or a command exiting 0. `env` sets variables in the app's environment, over its server's. `@ready_check` marks a method for readiness that needs logic (a
792
799
  sync one runs in a thread), and `@install_step(scope=...)` one a computer runs as it installs the
793
800
  app, after its run scripts, offline and without the class's `__init__`. Each runs on every OS the
@@ -821,13 +828,16 @@ desktop session, and `ready` (GET: an `AppReadiness` of `ready`, `problems`, `la
821
828
  and `timeout_s`), which runs the app's readiness checks then, at once, each within 10 s. A server
822
829
  that can't start, or whose start in the session fails (adding the launcher, say), writes why to
823
830
  `.agentenv/server.failure` in the folder and exits, and the driver reports that in its place. Once
824
- the X display is up, when `DISPLAY` names one, the server adds the app's launcher to the session user's
831
+ the session is up, the X display when `DISPLAY` names one or on Hyprland its Wayland display and its
832
+ IPC, the server adds the app's launcher to the session user's
825
833
  menu, which runs `python -m agentenv_protocol.computer.launch <folder>` to ask the server, so the
826
834
  server starts every copy of the app, and launches a `boot` app, once per boot. An app that runs with
827
835
  no desktop, a website browser alone, gets no launcher. The driver finds each app by the socket in its
828
836
  folder: the computer is ready once every app's server answers and each `boot` app, and each app with
829
837
  no launch, says it's ready; a `demand` app counts once something launches it. What the server does
830
- in the session (`agentenv_protocol.computer.session`) is Linux's.
838
+ in the session (`agentenv_protocol.computer.session`) is Linux's. On Hyprland it lists windows through
839
+ Hyprland's IPC (`hyprctl -j clients`), which sees native Wayland windows and XWayland's alike, where
840
+ X's `xprop` sees only XWayland's.
831
841
 
832
842
  The server serves the app's data plane and its skill. With `paths` alone, `data/add` writes the files
833
843
  it's given under them and refuses any other path, or one a link leads out of them; `data/get`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.490"
3
+ version = "0.1.491"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -80,7 +80,7 @@ def granted_headers(url: str, grants: dict[str | None, dict[str, str]]) -> dict[
80
80
  async def _rpc(base_url: str, method: str, params: dict, timeout: int, verify: bool,
81
81
  headers: dict[str, str] | None) -> dict:
82
82
  request = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params}
83
- url = f"{base_url}{RPC_PATH}"
83
+ url = f"{base_url.rstrip('/')}{RPC_PATH}"
84
84
  async with httpx.AsyncClient(verify=verify) as client:
85
85
  response = await client.post(url, json=request, timeout=timeout, headers=request_headers(url, headers))
86
86
  response.raise_for_status()
@@ -117,7 +117,7 @@ async def get_data(
117
117
 
118
118
  async def get_card(base_url: str, timeout: int = 10, verify: bool = True) -> dict:
119
119
  async with httpx.AsyncClient(verify=verify) as client:
120
- response = await client.get(f"{base_url}{WELL_KNOWN_PATH}", timeout=timeout)
120
+ response = await client.get(f"{base_url.rstrip('/')}{WELL_KNOWN_PATH}", timeout=timeout)
121
121
  response.raise_for_status()
122
122
  return response.json()
123
123
 
@@ -341,7 +341,7 @@ async def invoke_extension(base_url: str, card: dict, uri: str, params: dict | N
341
341
  raise ValueError(f"extension {uri} does not advertise method {method!r} (advertises: {offered})")
342
342
  if not op["endpoint"]:
343
343
  raise ValueError(f"extension {uri} has no endpoint in params")
344
- url = f"{base_url}{op['endpoint']}"
344
+ url = f"{base_url.rstrip('/')}{op['endpoint']}"
345
345
  sent = request_headers(url, headers)
346
346
  async with httpx.AsyncClient(verify=verify) as client:
347
347
  if op["method"] == "GET":
@@ -1,10 +1,12 @@
1
1
  """What a computer app's own server does in the desktop session on Linux and macOS (``session``): wait for the X
2
- session, launch the app into a session of its own and stop or pause it by its process group, add its freedesktop menu
3
- launcher and list the X session's windows."""
2
+ session, or on Hyprland for its Wayland and IPC sockets, launch the app into a session of its own and stop or pause it
3
+ by its process group, add its freedesktop menu launcher and list the session's windows, X's with ``xprop`` or
4
+ Hyprland's with ``hyprctl``."""
4
5
  from __future__ import annotations
5
6
 
6
7
  import asyncio
7
8
  import contextlib
9
+ import json
8
10
  import os
9
11
  import re
10
12
  import signal
@@ -21,9 +23,13 @@ WITH_FDS = Path(__file__).with_name("_with_fds.py")
21
23
 
22
24
 
23
25
  async def wait_until_up() -> None:
24
- """Until the X display's socket is there."""
25
- display = os.environ["DISPLAY"].lstrip(":").split(".")[0]
26
- while not Path(f"/tmp/.X11-unix/X{display}").exists():
26
+ """Until the session's sockets are there: on Hyprland its Wayland display's and its IPC's, else the X display's."""
27
+ if signature := _hyprland():
28
+ runtime = Path(os.environ["XDG_RUNTIME_DIR"])
29
+ sockets = [runtime / os.environ["WAYLAND_DISPLAY"], runtime / "hypr" / signature / ".socket.sock"]
30
+ else:
31
+ sockets = [Path(f"/tmp/.X11-unix/X{os.environ['DISPLAY'].lstrip(':').split('.')[0]}")]
32
+ while not all(socket.exists() for socket in sockets):
27
33
  await asyncio.sleep(POLL_S)
28
34
 
29
35
 
@@ -101,7 +107,11 @@ def add_launcher(name: str, folder: Path) -> Path:
101
107
  async def windows() -> list[dict[str, str]]:
102
108
  """The window manager's top-level windows, each as its WM_CLASS ``instance`` and ``class`` and its ``title``, read
103
109
  with ``xprop``: ``_NET_WM_NAME``, or ``WM_NAME`` for a window that sets only that; a window that closes while it's
104
- read is left out."""
110
+ read is left out. On Hyprland, its mapped clients as its IPC lists them, native Wayland and XWayland alike: its
111
+ ``class``, a Wayland app ID or an X window's WM_CLASS class, and its ``title``, and as its ``instance``, which a
112
+ Wayland window hasn't, its program's file name, as on Windows."""
113
+ if _hyprland():
114
+ return await _clients()
105
115
  listed = await _xprop("-root", "_NET_CLIENT_LIST")
106
116
  found = []
107
117
  for window in re.findall(r"0x[0-9a-f]+", listed.partition("#")[2]):
@@ -119,8 +129,37 @@ def _strings(value: str) -> list[str]:
119
129
  return [re.sub(r"\\(.)", r"\1", text) for text in re.findall(r'"((?:[^"\\]|\\.)*)"', value)]
120
130
 
121
131
 
132
+ def _hyprland() -> str | None:
133
+ """The Hyprland instance whose session this is, by its signature; None outside one."""
134
+ return os.environ.get("HYPRLAND_INSTANCE_SIGNATURE") or None
135
+
136
+
137
+ async def _clients() -> list[dict[str, str]]:
138
+ """Hyprland's mapped clients, by their program, class and title; none when its IPC doesn't answer."""
139
+ try:
140
+ clients = json.loads(await _output("hyprctl", "-j", "clients") or "[]")
141
+ except ValueError:
142
+ return []
143
+ return [{"instance": _program(client.get("pid")), "class": client.get("class") or "",
144
+ "title": client.get("title") or ""}
145
+ for client in clients if isinstance(client, dict) and client.get("mapped")]
146
+
147
+
148
+ def _program(pid: object) -> str:
149
+ """The file name of the program process ``pid`` runs, or "" when it can't be read."""
150
+ try:
151
+ return Path(os.readlink(f"/proc/{int(pid)}/exe")).name
152
+ except (OSError, TypeError, ValueError):
153
+ return ""
154
+
155
+
122
156
  async def _xprop(*args: str) -> str:
123
- process = await asyncio.create_subprocess_exec("xprop", *args, stdout=asyncio.subprocess.PIPE,
157
+ return await _output("xprop", *args)
158
+
159
+
160
+ async def _output(*argv: str) -> str:
161
+ """What ``argv`` prints, or nothing when it fails; killed when the listing it's part of is cut short."""
162
+ process = await asyncio.create_subprocess_exec(*argv, stdout=asyncio.subprocess.PIPE,
124
163
  stderr=asyncio.subprocess.DEVNULL)
125
164
  try:
126
165
  out, _ = await process.communicate()
@@ -187,7 +187,8 @@ class Launch(BaseModel):
187
187
  at: Literal["boot", "demand"] = "boot"
188
188
  command: list[str]
189
189
  #: Matches the app's window by ``class``, ``instance`` and ``title``, as given: on X11 its WM_CLASS class and
190
- #: instance; on Windows its window class and its program's file name, lowercased, such as ``notepad.exe``.
190
+ #: instance; on Hyprland its class, a Wayland app ID or an X window's WM_CLASS class, and its program's file name;
191
+ #: on Windows its window class and its program's file name, lowercased, such as ``notepad.exe``.
191
192
  window: Optional[dict[str, str]] = None
192
193
  ready: Optional[Ready] = None
193
194
  #: Variables set in the app's environment, over its server's.
@@ -80,14 +80,14 @@ class AppRuntime:
80
80
  self.checking: dict[str, asyncio.Future] = {}
81
81
 
82
82
  async def start(self) -> None:
83
- """Once its X display is up, when it has one, add the app's menu launcher on a desktop and launch a boot app,
84
- unless the computer has launched it since it booted, at boot or on demand, or its boot launch is held
85
- (``boot_launch_held``): a server that restarts leaves the app be, or with ``relaunch_on_start``, for an app
86
- only the server that launched it can talk to, stops it and launches it again, whether it was launched at boot
87
- or on demand."""
83
+ """Once its session is up, its X display or Hyprland, when it has one, add the app's menu launcher on a desktop
84
+ and launch a boot app, unless the computer has launched it since it booted, at boot or on demand, or its boot
85
+ launch is held (``boot_launch_held``): a server that restarts leaves the app be, or with ``relaunch_on_start``,
86
+ for an app only the server that launched it can talk to, stops it and launches it again, whether it was
87
+ launched at boot or on demand."""
88
88
  if self.launch is None:
89
89
  return
90
- if os.environ.get("DISPLAY"):
90
+ if os.environ.get("DISPLAY") or os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
91
91
  await session.wait_until_up()
92
92
  if self.desktop:
93
93
  session.add_launcher(os.environ["ENVIRONMENT_NAME"], Path.cwd())
@@ -116,8 +116,15 @@ class Probe:
116
116
  self.layout: dict = {}
117
117
  self.fixture_pid: str | None = None
118
118
  self.page = self.features.get("screen") == "page"
119
- self.fixture = "page_fixture.py" if self.page else "fixture.py"
120
- self.windows = (self.features.get("platform") or {}).get("os") == "windows"
119
+ platform = self.features.get("platform") or {}
120
+ #: Whether the desk is the GTK one: on Wayland, where Tk runs only under XWayland, which a Wayland driver may
121
+ #: not reach.
122
+ self.gtk = not self.page and platform.get("display") == "wayland"
123
+ self.fixture = "page_fixture.py" if self.page else "wayland_fixture.py" if self.gtk else "fixture.py"
124
+ self.windows = platform.get("os") == "windows"
125
+ #: The chord that selects a text box's text: Tk's Ctrl+/, which the Tk desk binds everywhere, or on the GTK
126
+ #: desk GTK's own Ctrl+A.
127
+ self.select_all = ["Control", "a"] if self.gtk else ["Control", "/"]
121
128
  self.desk = ""
122
129
  self.tab: str | None = None
123
130
  self.opened: list[str] = []
@@ -562,12 +569,13 @@ class Probe:
562
569
  # full: the fixture
563
570
 
564
571
  async def _stage(self) -> str | None:
565
- """The Fixture Desk started, or what stopped it: the Tk desk, or on a page, the HTML desk's server, with the
566
- desk opened in a tab of the probe's own."""
567
- source = resources.files(__package__).joinpath(self.fixture).read_bytes()
568
- added = await self.driver.add_file(f"{STAGE}/{self.fixture}", source)
569
- if "error" in added:
570
- return f"data/add refused it: {added['error'].get('message')}"
572
+ """The Fixture Desk started, or what stopped it: the Tk desk or on Wayland the GTK desk, either beside the layout
573
+ they share (desk.py), or on a page, the HTML desk's server, with the desk opened in a tab of the probe's own."""
574
+ for file in [self.fixture] if self.page else [self.fixture, "desk.py"]:
575
+ source = resources.files(__package__).joinpath(file).read_bytes()
576
+ added = await self.driver.add_file(f"{STAGE}/{file}", source)
577
+ if "error" in added:
578
+ return f"data/add refused it: {added['error'].get('message')}"
571
579
  if self.windows:
572
580
  await self.driver.run(*WIN_REMOVE, f"{STAGE}/state")
573
581
  started = await self.driver.run(*WIN_START, f"{STAGE}/{self.fixture}", f"{STAGE}/state", f"{STAGE}/fixture.log")
@@ -781,7 +789,7 @@ class Probe:
781
789
 
782
790
  async def clear() -> None:
783
791
  await focus("name")
784
- await self.act("computer_key", {"keys": ["Control", "/"]})
792
+ await self.act("computer_key", {"keys": self.select_all})
785
793
  await self.act("computer_key", {"keys": ["Backspace"]})
786
794
 
787
795
  async def unicode() -> str | None:
@@ -796,7 +804,7 @@ class Probe:
796
804
 
797
805
  async def chord() -> str | None:
798
806
  await focus("name")
799
- await self.act("computer_key", {"keys": ["Control", "/"]})
807
+ await self.act("computer_key", {"keys": self.select_all})
800
808
  await self.act("computer_key", {"keys": ["Backspace"]})
801
809
  return await self.settled(lambda s: s["name"] == "", lambda s: s["name"])
802
810
 
@@ -0,0 +1,65 @@
1
+ """The Fixture Desk's layout and what it records, shared by the Tk desk (fixture.py) and the GTK desk
2
+ (wayland_fixture.py), so the two can't drift apart. The probe stages it beside whichever desk it starts. It imports no
3
+ toolkit, since a Wayland computer may have no Tk."""
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+
10
+ #: The colours a grid cell cycles through, one more on each click.
11
+ COLOURS = ("#ffffff", "#ff0000", "#00aa00", "#0000ff")
12
+ CELL = 50
13
+ GRID = 8
14
+ #: Single-pixel markers on a black field, at odd and even pixels: (x, y) within the field, then the colour.
15
+ MARKERS = ((11, 11, "#ff0000"), (12, 11, "#00ff00"), (11, 12, "#0000ff"), (12, 12, "#ffff00"), (37, 20, "#ff00ff"),
16
+ (20, 37, "#00ffff"))
17
+ CLIPBOARD = "agentenv-probe clipboard"
18
+ MIN_SIZE = (1280, 800)
19
+ #: Where each widget's top left goes, the name box's label at "label" and the list's frame at "list".
20
+ AT = {"label": (40, 40), "name": (140, 40), "save": (560, 34), "subscribe": (40, 100), "notes": (760, 40),
21
+ "list": (40, 160), "grid": (400, 160), "pad": (840, 160), "wheel": (840, 500), "field": (1080, 500)}
22
+ #: The widgets sized in pixels: the list's frame, the drawing pad, the wheel and the marker field.
23
+ SIZE = {"list": {"width": 300, "height": 400}, "pad": {"width": 400, "height": 300},
24
+ "wheel": {"width": 200, "height": 120}, "field": {"width": 48, "height": 48}}
25
+ #: The list's rows, Item 1 to Item ITEMS.
26
+ ITEMS = 100
27
+ #: How many of the latest keys and drags state.json keeps, and about how many points of a drag's path.
28
+ KEYS_KEPT, DRAGS_KEPT, PATH_POINTS = 100, 20, 50
29
+ #: How often the desk reads inbox.txt.
30
+ INBOX_EVERY_MS = 200
31
+
32
+
33
+ def new_state(width: int, height: int, windowing: str) -> dict:
34
+ """What state.json holds before anything reaches the desk."""
35
+ return {"name": "", "notes": "", "keys": [], "saved_count": 0, "subscribed": False, "selected": None,
36
+ "scroll_top": 0.0, "cells": {}, "right_clicks": 0, "double_clicks": 0, "drags": [],
37
+ "wheel": {"up": 0, "down": 0, "left": 0, "right": 0}, "clipboard": None, "loaded": None,
38
+ "screen": [width, height], "windowing": windowing}
39
+
40
+
41
+ def write_state(state_dir: Path, state: dict) -> None:
42
+ """state.json replaced whole, so the probe never reads half of one."""
43
+ part = state_dir / ".state.json.part"
44
+ part.write_text(json.dumps(state))
45
+ os.replace(part, state_dir / "state.json")
46
+
47
+
48
+ def write_layout(state_dir: Path, boxes: dict, row_height: int, field: tuple[int, int]) -> None:
49
+ """layout.json: each widget's box on the screen, a list row's height, the markers' pixels from the field's top left,
50
+ and the cell size and clipboard text the probe checks against."""
51
+ layout = {**boxes, "list_row_height": row_height,
52
+ "markers": [[field[0] + x, field[1] + y, colour] for x, y, colour in MARKERS], "cell": CELL,
53
+ "clipboard": CLIPBOARD}
54
+ (state_dir / "layout.json").write_text(json.dumps(layout))
55
+
56
+
57
+ def record_key(state: dict, key: str) -> None:
58
+ state["keys"] = (state["keys"] + [key])[-KEYS_KEPT:]
59
+
60
+
61
+ def record_drag(state: dict, stroke: list) -> None:
62
+ """A finished drag: its ends, its number of points and its path thinned to about PATH_POINTS points."""
63
+ path = stroke[::max(1, len(stroke) // PATH_POINTS)] + stroke[-1:]
64
+ drag = {"start": stroke[0], "end": stroke[-1], "points": len(stroke), "path": path}
65
+ state["drags"] = (state["drags"] + [drag])[-DRAGS_KEPT:]
@@ -3,26 +3,19 @@ Desk, whose widgets' screen boxes are written to <state-dir>/layout.json and eve
3
3
  whatever lands in <state-dir>/inbox.txt, to <state-dir>/state.json, so the probe can aim at it and read back what
4
4
  arrived. It records the same effects under X11, macOS's Aqua and Windows, whose Tk names keys, binds select-all and
5
5
  reports the wheel differently. On X11 it stays above an app's windows (``keep_on_top``); elsewhere it's full screen.
6
- Run as ``python3 fixture.py <state-dir>``; it needs a 1280x800 screen or larger."""
6
+ Its layout and what it records are desk.py's, which the GTK desk shares. Run as ``python3 fixture.py <state-dir>``
7
+ beside desk.py; it needs a 1280x800 screen or larger."""
7
8
  from __future__ import annotations
8
9
 
9
10
  import ctypes
10
- import json
11
- import os
12
11
  import sys
13
12
  import time
14
13
  import tkinter as tk
15
14
  from pathlib import Path
16
15
 
17
- #: The colours a grid cell cycles through, one more on each click.
18
- COLOURS = ("#ffffff", "#ff0000", "#00aa00", "#0000ff")
19
- CELL = 50
20
- GRID = 8
21
- #: Single-pixel markers on a black field, at odd and even pixels: (x, y) within the field, then the colour.
22
- MARKERS = ((11, 11, "#ff0000"), (12, 11, "#00ff00"), (11, 12, "#0000ff"), (12, 12, "#ffff00"), (37, 20, "#ff00ff"),
23
- (20, 37, "#00ffff"))
24
- CLIPBOARD = "agentenv-probe clipboard"
25
- MIN_SIZE = (1280, 800)
16
+ from desk import (AT, CELL, CLIPBOARD, COLOURS, GRID, INBOX_EVERY_MS, ITEMS, MARKERS, MIN_SIZE, SIZE, new_state,
17
+ record_drag, record_key, write_layout, write_state)
18
+
26
19
  #: X11's keysyms for the keys Aqua's Tk names by their character.
27
20
  KEYSYMS = {",": "comma", " ": "space"}
28
21
  #: A wheel notch's <MouseWheel> delta where Tk scales the wheel: Windows', and Tk 9's everywhere.
@@ -46,15 +39,10 @@ def main(state_dir: Path) -> None:
46
39
  else:
47
40
  root.attributes("-fullscreen", True)
48
41
  root.configure(bg="#f0f0f0")
49
- state: dict = {"name": "", "notes": "", "keys": [], "saved_count": 0, "subscribed": False, "selected": None,
50
- "scroll_top": 0.0, "cells": {}, "right_clicks": 0, "double_clicks": 0, "drags": [],
51
- "wheel": {"up": 0, "down": 0, "left": 0, "right": 0}, "clipboard": None, "loaded": None,
52
- "screen": [width, height], "windowing": root.tk.call("tk", "windowingsystem")}
42
+ state = new_state(width, height, root.tk.call("tk", "windowingsystem"))
53
43
 
54
44
  def save() -> None:
55
- part = state_dir / ".state.json.part"
56
- part.write_text(json.dumps(state))
57
- os.replace(part, state_dir / "state.json")
45
+ write_state(state_dir, state)
58
46
 
59
47
  if width < MIN_SIZE[0] or height < MIN_SIZE[1]:
60
48
  state["error"] = f"the screen is {width}x{height}; the fixture needs {MIN_SIZE[0]}x{MIN_SIZE[1]}"
@@ -63,13 +51,13 @@ def main(state_dir: Path) -> None:
63
51
  return
64
52
 
65
53
  font = ("DejaVu Sans", 16)
66
- tk.Label(root, text="Name:", font=font, bg="#f0f0f0").place(x=40, y=40)
54
+ _place(tk.Label(root, text="Name:", font=font, bg="#f0f0f0"), "label")
67
55
  name = tk.Entry(root, font=font, width=30)
68
- name.place(x=140, y=40)
56
+ _place(name, "name")
69
57
 
70
58
  def key(event) -> None:
71
59
  state["name"] = name.get()
72
- state["keys"] = (state["keys"] + [KEYSYMS.get(event.keysym, event.keysym)])[-100:]
60
+ record_key(state, KEYSYMS.get(event.keysym, event.keysym))
73
61
  state["clipboard"] = _clipboard(root)
74
62
  save()
75
63
 
@@ -88,7 +76,7 @@ def main(state_dir: Path) -> None:
88
76
  save()
89
77
 
90
78
  button = tk.Button(root, text="Save", font=font, command=on_save, width=8)
91
- button.place(x=560, y=34)
79
+ _place(button, "save")
92
80
 
93
81
  subscribed = tk.BooleanVar(value=False)
94
82
 
@@ -98,10 +86,10 @@ def main(state_dir: Path) -> None:
98
86
 
99
87
  subscribe = tk.Checkbutton(root, text="Subscribe", font=font, variable=subscribed, command=on_subscribe,
100
88
  bg="#f0f0f0")
101
- subscribe.place(x=40, y=100)
89
+ _place(subscribe, "subscribe")
102
90
 
103
91
  notes = tk.Text(root, font=("DejaVu Sans", 14), width=28, height=3)
104
- notes.place(x=760, y=40)
92
+ _place(notes, "notes")
105
93
 
106
94
  def note(event) -> None:
107
95
  state["notes"] = notes.get("1.0", "end-1c")
@@ -110,7 +98,7 @@ def main(state_dir: Path) -> None:
110
98
  notes.bind("<KeyRelease>", note)
111
99
 
112
100
  frame = tk.Frame(root)
113
- frame.place(x=40, y=160, width=300, height=400)
101
+ _place(frame, "list", **SIZE["list"])
114
102
  listbox = tk.Listbox(frame, font=("DejaVu Sans", 14), exportselection=False)
115
103
  listbox.pack(side="left", fill="both", expand=True)
116
104
  bar = tk.Scrollbar(frame, command=listbox.yview)
@@ -122,7 +110,7 @@ def main(state_dir: Path) -> None:
122
110
  save()
123
111
 
124
112
  listbox.configure(yscrollcommand=scrolled)
125
- for i in range(1, 101):
113
+ for i in range(1, ITEMS + 1):
126
114
  listbox.insert("end", f"Item {i}")
127
115
 
128
116
  def select(event) -> None:
@@ -133,7 +121,7 @@ def main(state_dir: Path) -> None:
133
121
  listbox.bind("<<ListboxSelect>>", select)
134
122
 
135
123
  grid = tk.Canvas(root, width=GRID * CELL, height=GRID * CELL, bg="#ffffff", highlightthickness=0)
136
- grid.place(x=400, y=160)
124
+ _place(grid, "grid")
137
125
  for row in range(GRID):
138
126
  for col in range(GRID):
139
127
  grid.create_rectangle(col * CELL, row * CELL, (col + 1) * CELL, (row + 1) * CELL, fill=COLOURS[0],
@@ -155,8 +143,8 @@ def main(state_dir: Path) -> None:
155
143
  grid.bind("<Button-3>", lambda e: cell(e, True))
156
144
  grid.bind("<Double-Button-1>", double)
157
145
 
158
- pad = tk.Canvas(root, width=400, height=300, bg="#fffbe6", highlightthickness=0)
159
- pad.place(x=840, y=160)
146
+ pad = tk.Canvas(root, **SIZE["pad"], bg="#fffbe6", highlightthickness=0)
147
+ _place(pad, "pad")
160
148
  stroke: list = []
161
149
 
162
150
  def press(event) -> None:
@@ -170,8 +158,7 @@ def main(state_dir: Path) -> None:
170
158
  def release(event) -> None:
171
159
  if stroke:
172
160
  stroke.append((event.x, event.y))
173
- state["drags"] = (state["drags"] + [{"start": stroke[0], "end": stroke[-1], "points": len(stroke),
174
- "path": stroke[::max(1, len(stroke) // 50)] + stroke[-1:]}])[-20:]
161
+ record_drag(state, stroke)
175
162
  save()
176
163
  stroke.clear()
177
164
 
@@ -179,8 +166,8 @@ def main(state_dir: Path) -> None:
179
166
  pad.bind("<B1-Motion>", motion)
180
167
  pad.bind("<ButtonRelease-1>", release)
181
168
 
182
- wheel = tk.Canvas(root, width=200, height=120, bg="#e6f0ff", highlightthickness=0)
183
- wheel.place(x=840, y=500)
169
+ wheel = tk.Canvas(root, **SIZE["wheel"], bg="#e6f0ff", highlightthickness=0)
170
+ _place(wheel, "wheel")
184
171
 
185
172
  def wheeled(horizontal: bool, steps: int) -> None:
186
173
  way = ("left" if horizontal else "up") if steps > 0 else ("right" if horizontal else "down")
@@ -202,8 +189,8 @@ def main(state_dir: Path) -> None:
202
189
  if tk.TkVersion >= 8.7:
203
190
  wheel.bind("<TouchpadScroll>", lambda e: scrolled(e.delta))
204
191
 
205
- field = tk.Canvas(root, width=48, height=48, bg="#000000", highlightthickness=0, bd=0)
206
- field.place(x=1080, y=500)
192
+ field = tk.Canvas(root, **SIZE["field"], bg="#000000", highlightthickness=0, bd=0)
193
+ _place(field, "field")
207
194
  for x, y, colour in MARKERS:
208
195
  field.create_rectangle(x, y, x + 1, y + 1, fill=colour, outline="", width=0)
209
196
 
@@ -212,28 +199,29 @@ def main(state_dir: Path) -> None:
212
199
  if message.exists() and (text := message.read_text()) != state["loaded"]:
213
200
  state["loaded"] = text
214
201
  save()
215
- root.after(200, poll_inbox)
202
+ root.after(INBOX_EVERY_MS, poll_inbox)
216
203
 
217
- def write_layout() -> None:
204
+ def layout() -> None:
218
205
  root.update_idletasks()
219
206
  boxes = {key: [w.winfo_rootx(), w.winfo_rooty(), w.winfo_width(), w.winfo_height()]
220
207
  for key, w in (("name", name), ("save", button), ("subscribe", subscribe), ("notes", notes),
221
208
  ("list", listbox), ("grid", grid), ("pad", pad), ("wheel", wheel), ("field", field))}
222
- boxes["list_row_height"] = listbox.bbox(0)[3] if listbox.bbox(0) else 0
223
- boxes["markers"] = [[field.winfo_rootx() + x, field.winfo_rooty() + y, colour] for x, y, colour in MARKERS]
224
- boxes["cell"] = CELL
225
- boxes["clipboard"] = CLIPBOARD
226
209
  save()
227
- (state_dir / "layout.json").write_text(json.dumps(boxes))
210
+ write_layout(state_dir, boxes, listbox.bbox(0)[3] if listbox.bbox(0) else 0,
211
+ (field.winfo_rootx(), field.winfo_rooty()))
228
212
 
229
213
  root.clipboard_clear()
230
214
  root.clipboard_append(CLIPBOARD)
231
215
  state["clipboard"] = CLIPBOARD
232
- root.after(300, write_layout)
216
+ root.after(300, layout)
233
217
  root.after(500, poll_inbox)
234
218
  root.mainloop()
235
219
 
236
220
 
221
+ def _place(widget: tk.Widget, at: str, **size: int) -> None:
222
+ widget.place(x=AT[at][0], y=AT[at][1], **size)
223
+
224
+
237
225
  def keep_on_top(root: tk.Tk) -> None:
238
226
  """Keep the fixture over the windows an app opens, which would take the probe's clicks: maximized and above other
239
227
  windows rather than full screen, since xfwm4 keeps a window an app puts above over a full-screen one, and raised