agentenv-framework-protocol 0.1.484__tar.gz → 0.1.486__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 (172) hide show
  1. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/PKG-INFO +1 -1
  2. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/pyproject.toml +1 -1
  3. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/__init__.py +4 -1
  4. agentenv_framework_protocol-0.1.486/src/agentenv_protocol/computer/_job.py +110 -0
  5. agentenv_framework_protocol-0.1.486/src/agentenv_protocol/computer/_reaping.py +53 -0
  6. agentenv_framework_protocol-0.1.484/src/agentenv_protocol/computer/session.py → agentenv_framework_protocol-0.1.486/src/agentenv_protocol/computer/_session_posix.py +7 -34
  7. agentenv_framework_protocol-0.1.486/src/agentenv_protocol/computer/_session_windows.py +317 -0
  8. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/app.py +32 -2
  9. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/app_runtime.py +5 -2
  10. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/apps.py +16 -12
  11. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/launch.py +4 -6
  12. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/serve_app.py +13 -6
  13. agentenv_framework_protocol-0.1.486/src/agentenv_protocol/computer/served.py +114 -0
  14. agentenv_framework_protocol-0.1.486/src/agentenv_protocol/computer/session.py +16 -0
  15. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/svc.py +17 -11
  16. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_app_server.py +20 -6
  17. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_declare.py +18 -3
  18. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_serve.py +45 -9
  19. agentenv_framework_protocol-0.1.486/tests/test_computer_served.py +103 -0
  20. agentenv_framework_protocol-0.1.486/tests/test_computer_session_windows.py +204 -0
  21. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_svc_forward.py +36 -19
  22. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/.gitignore +0 -0
  23. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/LICENSE +0 -0
  24. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/NOTICE +0 -0
  25. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/README.md +0 -0
  26. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/THIRD_PARTY_NOTICES.md +0 -0
  27. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/__init__.py +0 -0
  28. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/advanced_agent.py +0 -0
  29. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/basic_agent.py +0 -0
  30. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/custom_extensions_agent.py +0 -0
  31. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/multimodal_agent.py +0 -0
  32. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/reference_model.py +0 -0
  33. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/streaming_agent.py +0 -0
  34. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/.gitignore +0 -0
  35. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/Dockerfile +0 -0
  36. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/Dockerfile.dockerignore +0 -0
  37. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/README.md +0 -0
  38. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/data/seed.json +0 -0
  39. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/env.toml +0 -0
  40. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/site.py +0 -0
  41. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/web/index.html +0 -0
  42. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/web/package-lock.json +0 -0
  43. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/web/package.json +0 -0
  44. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/web/src/main.js +0 -0
  45. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/web/src/style.css +0 -0
  46. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/examples/website/notes/web/vite.config.js +0 -0
  47. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/__init__.py +0 -0
  48. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  49. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  50. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  51. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  52. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  53. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  54. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  55. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  56. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  57. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  58. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  59. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/agent_env_environment.py +0 -0
  60. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/browser_cdp.py +0 -0
  61. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/client.py +0 -0
  62. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/_declare.py +0 -0
  63. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/_linux.py +0 -0
  64. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/_load.py +0 -0
  65. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/_macos.py +0 -0
  66. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/_with_fds.py +0 -0
  67. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/_x11.py +0 -0
  68. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/app_data.py +0 -0
  69. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/card.py +0 -0
  70. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/checks.py +0 -0
  71. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  72. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  73. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
  74. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  75. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
  76. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  77. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/page_fixture.py +0 -0
  78. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  79. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/dataplane.py +0 -0
  80. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/driver.py +0 -0
  81. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/errors.py +0 -0
  82. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/extensions.py +0 -0
  83. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/frames.py +0 -0
  84. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/keys.py +0 -0
  85. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/manifest.py +0 -0
  86. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/operations.py +0 -0
  87. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/platform.py +0 -0
  88. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/prefix.py +0 -0
  89. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/queue.py +0 -0
  90. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/recording.py +0 -0
  91. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/results.py +0 -0
  92. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/run_install_step.py +0 -0
  93. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/runtime.py +0 -0
  94. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/seed.py +0 -0
  95. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/serve.py +0 -0
  96. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/settings.py +0 -0
  97. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/skill.py +0 -0
  98. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/spaces.py +0 -0
  99. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/computer/tools.py +0 -0
  100. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/errors.py +0 -0
  101. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/manifest.py +0 -0
  102. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/preflight.py +0 -0
  103. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/tool_access.py +0 -0
  104. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/tool_client.py +0 -0
  105. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/tool_context.py +0 -0
  106. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/transfers.py +0 -0
  107. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/types.py +0 -0
  108. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/__init__.py +0 -0
  109. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/clock.py +0 -0
  110. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/conformance.py +0 -0
  111. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/environment.py +0 -0
  112. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/errors.py +0 -0
  113. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/readiness.py +0 -0
  114. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/routes.py +0 -0
  115. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website/session.py +0 -0
  116. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website_browser/__init__.py +0 -0
  117. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website_browser/control.py +0 -0
  118. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website_browser/data.py +0 -0
  119. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website_browser/devtools.py +0 -0
  120. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website_browser/environment.py +0 -0
  121. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/src/agentenv_protocol/website_browser/shapes.py +0 -0
  122. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/computer_fake.py +0 -0
  123. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/probe_fake.py +0 -0
  124. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_agent.py +0 -0
  125. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_agent_examples.py +0 -0
  126. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_cdp_config.py +0 -0
  127. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_object_transfers.py +0 -0
  128. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_peer_agents.py +0 -0
  129. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_proxy.py +0 -0
  130. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_staging.py +0 -0
  131. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_a2a_trajectory_log.py +0 -0
  132. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_agent_env_environment.py +0 -0
  133. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_browser_cdp.py +0 -0
  134. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_browser_data.py +0 -0
  135. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_browser_state.py +0 -0
  136. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_client.py +0 -0
  137. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_app_data.py +0 -0
  138. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_conformance.py +0 -0
  139. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_conformance_checks.py +0 -0
  140. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_conformance_page.py +0 -0
  141. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_conformance_recording.py +0 -0
  142. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_dataplane.py +0 -0
  143. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_driver.py +0 -0
  144. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_extensions.py +0 -0
  145. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_frames.py +0 -0
  146. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_macos.py +0 -0
  147. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_page.py +0 -0
  148. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_queue.py +0 -0
  149. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_recording.py +0 -0
  150. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_seed.py +0 -0
  151. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_spaces.py +0 -0
  152. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_computer_tool_prefix.py +0 -0
  153. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_control_token.py +0 -0
  154. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_data_objects.py +0 -0
  155. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_errors.py +0 -0
  156. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_extension_methods.py +0 -0
  157. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_intake.py +0 -0
  158. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_manifest.py +0 -0
  159. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_preflight.py +0 -0
  160. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_tool_access.py +0 -0
  161. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_tool_annotations.py +0 -0
  162. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_tool_client.py +0 -0
  163. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_tool_context.py +0 -0
  164. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_tool_context_fastmcp.py +0 -0
  165. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_tool_context_injection.py +0 -0
  166. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_website_api.py +0 -0
  167. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_website_browser.py +0 -0
  168. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_website_conformance.py +0 -0
  169. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_website_environment.py +0 -0
  170. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_website_mcp.py +0 -0
  171. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/tests/test_website_notes_example.py +0 -0
  172. {agentenv_framework_protocol-0.1.484 → agentenv_framework_protocol-0.1.486}/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.484
3
+ Version: 0.1.486
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.484"
3
+ version = "0.1.486"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -21,11 +21,13 @@ from .app import (
21
21
  Ready,
22
22
  RunScript,
23
23
  Service,
24
+ Windows,
24
25
  computer_app,
25
26
  install,
26
27
  install_step,
27
28
  linux,
28
29
  ready_check,
30
+ windows,
29
31
  )
30
32
  from .errors import (
31
33
  BackgroundUnavailable,
@@ -123,7 +125,8 @@ def __getattr__(name: str) -> Any:
123
125
  __all__ = [
124
126
  "ComputerDriverEnvironment", "computer_driver", "DEFAULT_IDENTITY", "DriverDeclaration", "MacOSDriver",
125
127
  "X11Driver",
126
- "ComputerAppEnvironment", "computer_app", "install", "linux", "install_step", "ready_check", "Linux", "Service",
128
+ "ComputerAppEnvironment", "computer_app", "install", "linux", "windows", "install_step", "ready_check", "Linux",
129
+ "Windows", "Service",
127
130
  "Launch", "Ready", "Packages", "AptSource", "Download", "RunScript",
128
131
  "AppDeclaration", "AppOs",
129
132
  "APP_SETTINGS_ENV", "part_bytes",
@@ -0,0 +1,110 @@
1
+ """Run a Windows app in a Job object that holds it and everything it starts: ``python -I -S _job.py -- <argv>``. The
2
+ Windows ``session`` launches every app through it, by path, so it loads nothing but the standard library.
3
+
4
+ It names the job for its own pid (``job_name``), joins the job, then starts the app, so the app and whatever it starts
5
+ are in the job; it says ``started`` once the app has, or why it couldn't on stderr, and stays until the job holds no
6
+ process but itself, then exits with the app's exit code. It holds the job's only handle, which ends the job when it
7
+ closes: the app lives as long as this does, outliving the server that launched it, as a POSIX app's session does, and a
8
+ server, restarted or not, finds the job by name to pause or stop it."""
9
+ from __future__ import annotations
10
+
11
+ import ctypes
12
+ import os
13
+ import subprocess
14
+ import sys
15
+ import time
16
+
17
+ #: How often the job is looked at for processes besides this one, once the app has exited.
18
+ POLL_S = 0.2
19
+ #: ``JOBOBJECT_INFORMATION_CLASS`` values and the limit and access flags used.
20
+ BASIC_ACCOUNTING_INFORMATION, BASIC_PROCESS_ID_LIST, EXTENDED_LIMIT_INFORMATION = 1, 3, 9
21
+ KILL_ON_JOB_CLOSE = 0x2000
22
+ JOB_OBJECT_QUERY, JOB_OBJECT_TERMINATE = 0x0004, 0x0008
23
+ STARTED = b"started"
24
+
25
+
26
+ class BasicLimits(ctypes.Structure):
27
+ _fields_ = [("PerProcessUserTimeLimit", ctypes.c_int64), ("PerJobUserTimeLimit", ctypes.c_int64),
28
+ ("LimitFlags", ctypes.c_uint32), ("MinimumWorkingSetSize", ctypes.c_size_t),
29
+ ("MaximumWorkingSetSize", ctypes.c_size_t), ("ActiveProcessLimit", ctypes.c_uint32),
30
+ ("Affinity", ctypes.c_size_t), ("PriorityClass", ctypes.c_uint32), ("SchedulingClass", ctypes.c_uint32)]
31
+
32
+
33
+ class IoCounters(ctypes.Structure):
34
+ _fields_ = [(name, ctypes.c_uint64) for name in ("ReadOperationCount", "WriteOperationCount", "OtherOperationCount",
35
+ "ReadTransferCount", "WriteTransferCount", "OtherTransferCount")]
36
+
37
+
38
+ class ExtendedLimits(ctypes.Structure):
39
+ _fields_ = [("BasicLimitInformation", BasicLimits), ("IoInfo", IoCounters),
40
+ ("ProcessMemoryLimit", ctypes.c_size_t), ("JobMemoryLimit", ctypes.c_size_t),
41
+ ("PeakProcessMemoryUsed", ctypes.c_size_t), ("PeakJobMemoryUsed", ctypes.c_size_t)]
42
+
43
+
44
+ class Accounting(ctypes.Structure):
45
+ _fields_ = [("TotalUserTime", ctypes.c_int64), ("TotalKernelTime", ctypes.c_int64),
46
+ ("ThisPeriodTotalUserTime", ctypes.c_int64), ("ThisPeriodTotalKernelTime", ctypes.c_int64),
47
+ ("TotalPageFaultCount", ctypes.c_uint32), ("TotalProcesses", ctypes.c_uint32),
48
+ ("ActiveProcesses", ctypes.c_uint32), ("TotalTerminatedProcesses", ctypes.c_uint32)]
49
+
50
+
51
+ def job_name(pid: int) -> str:
52
+ """The name of the job the helper running as ``pid`` holds, in the session's own namespace."""
53
+ return f"Local\\agentenv-app-{pid}"
54
+
55
+
56
+ def kernel32() -> ctypes.WinDLL:
57
+ """``kernel32``, its calls typed as this module and ``session`` use them."""
58
+ dll = ctypes.WinDLL("kernel32", use_last_error=True)
59
+ handle, dword, wide = ctypes.c_void_p, ctypes.c_uint32, ctypes.c_wchar_p
60
+ dll.CreateJobObjectW.argtypes, dll.CreateJobObjectW.restype = [ctypes.c_void_p, wide], handle
61
+ dll.OpenJobObjectW.argtypes, dll.OpenJobObjectW.restype = [dword, ctypes.c_int, wide], handle
62
+ dll.SetInformationJobObject.argtypes = [handle, ctypes.c_int, ctypes.c_void_p, dword]
63
+ dll.QueryInformationJobObject.argtypes = [handle, ctypes.c_int, ctypes.c_void_p, dword, ctypes.c_void_p]
64
+ dll.AssignProcessToJobObject.argtypes = [handle, handle]
65
+ dll.TerminateJobObject.argtypes = [handle, ctypes.c_uint]
66
+ dll.GetCurrentProcess.restype = handle
67
+ dll.CloseHandle.argtypes = [handle]
68
+ dll.OpenProcess.argtypes, dll.OpenProcess.restype = [dword, ctypes.c_int, dword], handle
69
+ dll.QueryFullProcessImageNameW.argtypes = [handle, dword, wide, ctypes.c_void_p]
70
+ dll.WaitForMultipleObjects.argtypes, dll.WaitForMultipleObjects.restype = [dword, ctypes.c_void_p, ctypes.c_int,
71
+ dword], dword
72
+ return dll
73
+
74
+
75
+ def active_processes(dll: ctypes.WinDLL, job: int) -> int:
76
+ """How many processes the job ``job`` holds now."""
77
+ accounting = Accounting()
78
+ if not dll.QueryInformationJobObject(job, BASIC_ACCOUNTING_INFORMATION, ctypes.byref(accounting),
79
+ ctypes.sizeof(accounting), None):
80
+ raise ctypes.WinError(ctypes.get_last_error())
81
+ return accounting.ActiveProcesses
82
+
83
+
84
+ def main(argv: list[str]) -> None:
85
+ command = argv[argv.index("--") + 1:]
86
+ dll = kernel32()
87
+ job = dll.CreateJobObjectW(None, job_name(os.getpid()))
88
+ limits = ExtendedLimits()
89
+ limits.BasicLimitInformation.LimitFlags = KILL_ON_JOB_CLOSE
90
+ if not job or not dll.SetInformationJobObject(job, EXTENDED_LIMIT_INFORMATION, ctypes.byref(limits),
91
+ ctypes.sizeof(limits)) \
92
+ or not dll.AssignProcessToJobObject(job, dll.GetCurrentProcess()):
93
+ raise ctypes.WinError(ctypes.get_last_error())
94
+ try:
95
+ app = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
96
+ stderr=subprocess.DEVNULL)
97
+ except OSError as e:
98
+ sys.stderr.write(f"{type(e).__name__}: {e}\n")
99
+ sys.exit(2)
100
+ sys.stdout.buffer.write(STARTED + b"\n")
101
+ sys.stdout.flush()
102
+ code = app.wait()
103
+ while active_processes(dll, job) > 1:
104
+ time.sleep(POLL_S)
105
+ # An exit code is 32 bits, which Python's exit takes as a signed number.
106
+ sys.exit(code - (1 << 32) if code >= 1 << 31 else code)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main(sys.argv[1:])
@@ -0,0 +1,53 @@
1
+ """The processes a computer app's server launched, each reaped once it exits, so its exit code can be asked for; shared
2
+ by every OS's session (``session``)."""
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Optional
7
+
8
+ #: The processes launched into the session, kept until they exit so each is reaped.
9
+ _running: set[asyncio.Task] = set()
10
+ #: Each launched process's reaping, by pid, until its exit is asked for: its return code, negative for a signal.
11
+ _exits: dict[int, asyncio.Future] = {}
12
+ #: How many exits no one has asked for are kept, the latest.
13
+ KEPT_EXITS = 16
14
+
15
+
16
+ def reap(process: asyncio.subprocess.Process) -> None:
17
+ """Reap ``process`` once it exits, its exit kept until it's asked for or ``KEPT_EXITS`` later ones push it out; any
18
+ pipe it was started with is read to its end, so it closes with the process."""
19
+ reaped = asyncio.ensure_future(_exited(process))
20
+ _running.add(reaped)
21
+ reaped.add_done_callback(_running.discard)
22
+ _exits[process.pid] = reaped
23
+ reaped.add_done_callback(lambda _: _forget_old_exits())
24
+
25
+
26
+ async def _exited(process: asyncio.subprocess.Process) -> int:
27
+ await process.communicate()
28
+ return process.returncode
29
+
30
+
31
+ def forget(pid: int) -> None:
32
+ """No one asks how ``pid`` exited: a process the server stopped."""
33
+ _exits.pop(pid, None)
34
+
35
+
36
+ def _forget_old_exits() -> None:
37
+ """Drop all but the latest ``KEPT_EXITS`` exits no one has asked for."""
38
+ for pid in [pid for pid, reaped in _exits.items() if reaped.done()][:-KEPT_EXITS]:
39
+ del _exits[pid]
40
+
41
+
42
+ async def returncode(pid: int, timeout_s: float) -> Optional[int]:
43
+ """The return code the process ``launch`` started as ``pid`` exited with, negative for the signal that ended it,
44
+ once it's reaped within ``timeout_s``; None for one ``launch`` didn't start or that hasn't exited."""
45
+ reaped = _exits.get(pid)
46
+ if reaped is None:
47
+ return None
48
+ try:
49
+ code = await asyncio.wait_for(asyncio.shield(reaped), timeout_s)
50
+ except TimeoutError:
51
+ return None
52
+ _exits.pop(pid, None)
53
+ return code
@@ -1,5 +1,6 @@
1
- """What a computer app's own server does in the desktop session, the Linux way: wait for the session, launch the app
2
- into it and stop it, add its menu launcher and list the session's windows. Another OS type brings its own."""
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."""
3
4
  from __future__ import annotations
4
5
 
5
6
  import asyncio
@@ -11,16 +12,12 @@ import sys
11
12
  from pathlib import Path
12
13
  from typing import Iterator, Optional
13
14
 
15
+ from . import _reaping
16
+
14
17
  #: How often a starting session is looked for.
15
18
  POLL_S = 0.2
16
19
  #: Moves the fds an app is launched with into place, run on its own so it loads nothing but the standard library.
17
20
  WITH_FDS = Path(__file__).with_name("_with_fds.py")
18
- #: The processes launched into the session, kept until they exit so each is reaped.
19
- _running: set[asyncio.Task] = set()
20
- #: Each launched process's reaping, by pid, until its exit is asked for: its return code, negative for a signal.
21
- _exits: dict[int, asyncio.Future] = {}
22
- #: How many exits no one has asked for are kept, the latest.
23
- KEPT_EXITS = 16
24
21
 
25
22
 
26
23
  async def wait_until_up() -> None:
@@ -39,38 +36,14 @@ async def launch(argv: list[str], cwd: Path, env: dict[str, str], fds: Optional[
39
36
  process = await asyncio.create_subprocess_exec(
40
37
  *argv, cwd=cwd, env={**os.environ, **env}, start_new_session=True, stdin=asyncio.subprocess.DEVNULL,
41
38
  stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL, pass_fds=tuple((fds or {}).values()))
42
- reaped = asyncio.ensure_future(process.wait())
43
- _running.add(reaped)
44
- reaped.add_done_callback(_running.discard)
45
- _exits[process.pid] = reaped
46
- reaped.add_done_callback(lambda _: _forget_old_exits())
39
+ _reaping.reap(process)
47
40
  return process.pid
48
41
 
49
42
 
50
- def _forget_old_exits() -> None:
51
- """Drop all but the latest ``KEPT_EXITS`` exits no one has asked for."""
52
- for pid in [pid for pid, reaped in _exits.items() if reaped.done()][:-KEPT_EXITS]:
53
- del _exits[pid]
54
-
55
-
56
- async def returncode(pid: int, timeout_s: float) -> Optional[int]:
57
- """The return code the process ``launch`` started as ``pid`` exited with, negative for the signal that ended it,
58
- once it's reaped within ``timeout_s``; None for one ``launch`` didn't start or that hasn't exited."""
59
- reaped = _exits.get(pid)
60
- if reaped is None:
61
- return None
62
- try:
63
- code = await asyncio.wait_for(asyncio.shield(reaped), timeout_s)
64
- except TimeoutError:
65
- return None
66
- _exits.pop(pid, None)
67
- return code
68
-
69
-
70
43
  async def stop(pid: int, timeout_s: float) -> None:
71
44
  """End the session ``launch`` started as ``pid``, every process in it: asked to exit, a paused one continued so it
72
45
  can, then killed after ``timeout_s``. No one asks how it exited once it's stopped."""
73
- _exits.pop(pid, None)
46
+ _reaping.forget(pid)
74
47
  try:
75
48
  os.killpg(pid, signal.SIGTERM)
76
49
  except ProcessLookupError:
@@ -0,0 +1,317 @@
1
+ """What a computer app's own server does in the desktop session on Windows (``session``). agentenv-session starts the
2
+ server in the session user's sign-in, so the session is up and its desktop is the user's. An app launches through
3
+ ``_job`` into a Job object that holds it and everything it starts, found by the helper's pid: stopping it asks its
4
+ windows to close, then ends the job after the timeout, and pausing it suspends each of its processes. Its menu launcher
5
+ is a Start Menu shortcut, and the session's windows are listed with ``EnumWindows``, each by its class, its program's
6
+ file name and its title."""
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import contextlib
11
+ import ctypes
12
+ import os
13
+ import re
14
+ import subprocess
15
+ import sys
16
+ import time
17
+ from dataclasses import dataclass
18
+ from pathlib import Path
19
+ from typing import Iterator, NamedTuple, Optional
20
+
21
+ from . import _job, _reaping
22
+
23
+ #: The helper that runs an app in its job, run on its own so it loads nothing but the standard library.
24
+ JOB = Path(__file__).with_name("_job.py")
25
+ #: The interpreter the helper runs on: the base one, as a venv's python.exe is a launcher that runs it as a child, and
26
+ #: the helper names its job for its own pid, which must be the pid a launch returns.
27
+ PYTHON = Path(sys.base_prefix) / "python.exe"
28
+ #: How long the helper may take to start the app and say so.
29
+ START_TIMEOUT_S = 30.0
30
+ #: How often a stopping app is looked at for having ended.
31
+ POLL_S = 0.2
32
+ #: How long a job ended by force is waited on for each of its processes to have exited, so none still writes once a
33
+ #: stop returns and whatever they had open is closed, as a reset then puts the app's files back.
34
+ ENDED_TIMEOUT_S = 5.0
35
+ WM_CLOSE = 0x0010
36
+ #: A Start Menu shortcut, from its path, program, arguments and folder in the environment, as PowerShell writes one.
37
+ SHORTCUT = ("$s = (New-Object -ComObject WScript.Shell).CreateShortcut($env:AGENTENV_SHORTCUT); "
38
+ "$s.TargetPath = $env:AGENTENV_TARGET; $s.Arguments = $env:AGENTENV_ARGUMENTS; "
39
+ "$s.WorkingDirectory = $env:AGENTENV_FOLDER; $s.Save()")
40
+ #: What a file name can't hold on Windows.
41
+ UNSAFE = re.compile(r'[\\/:*?"<>|\x00-\x1f]')
42
+
43
+
44
+ async def wait_until_up() -> None:
45
+ """At once: the server runs in the session user's sign-in, so the session is up."""
46
+
47
+
48
+ async def launch(argv: list[str], cwd: Path, env: dict[str, str], fds: Optional[dict[int, int]] = None) -> int:
49
+ """Start ``argv`` in ``cwd`` in a job of its own (``_job``), which outlives the server, with the server's
50
+ environment and ``env``; the helper's pid, by which the job is found. A Windows app can't be handed fds."""
51
+ if fds:
52
+ raise NotImplementedError("a Windows app can't be handed the server's fds")
53
+ process = await asyncio.create_subprocess_exec(
54
+ str(PYTHON), "-I", "-S", str(JOB), "--", *argv, cwd=cwd, env={**os.environ, **env},
55
+ stdin=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
56
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW)
57
+ try:
58
+ said = await asyncio.wait_for(process.stdout.readline(), START_TIMEOUT_S)
59
+ except BaseException:
60
+ # Its job's only handle closes with it, which ends whatever it started.
61
+ with contextlib.suppress(ProcessLookupError):
62
+ process.kill()
63
+ _reaping.reap(process)
64
+ raise
65
+ if said.strip() != _job.STARTED:
66
+ why = ((await process.stderr.read()).decode(errors="replace").strip().splitlines() or [""])[-1]
67
+ await process.wait()
68
+ raise OSError(f"couldn't start {argv[0]}: {why or f'its job helper exited {process.returncode}'}")
69
+ _reaping.reap(process)
70
+ return process.pid
71
+
72
+
73
+ async def stop(pid: int, timeout_s: float) -> None:
74
+ """End the job the helper ``pid`` holds, every process in it: its windows asked to close, then the job ended after
75
+ ``timeout_s``, or at once when it has no window to close, and waited on until each process it held has exited and
76
+ it holds none. No one asks how it exited once it's stopped."""
77
+ _reaping.forget(pid)
78
+ job = _win32.open_job(pid)
79
+ if job is None:
80
+ return
81
+ try:
82
+ windows = [window for window in await asyncio.to_thread(_win32.windows) if window.pid in _app_pids(job, pid)]
83
+ for window in windows:
84
+ _win32.post_close(window.handle)
85
+ deadline = time.monotonic() + (timeout_s if windows else 0)
86
+ while _app_pids(job, pid) and time.monotonic() < deadline:
87
+ await asyncio.sleep(POLL_S)
88
+ if _app_pids(job, pid):
89
+ ending = _win32.open_processes(_win32.job_pids(job))
90
+ try:
91
+ _win32.terminate_job(job)
92
+ deadline = time.monotonic() + ENDED_TIMEOUT_S
93
+ await asyncio.to_thread(_win32.wait_exited, ending, ENDED_TIMEOUT_S)
94
+ while _win32.job_pids(job) and time.monotonic() < deadline:
95
+ await asyncio.sleep(POLL_S)
96
+ finally:
97
+ for process in ending:
98
+ _win32.close(process)
99
+ finally:
100
+ _win32.close(job)
101
+
102
+
103
+ @contextlib.contextmanager
104
+ def paused(pid: int) -> Iterator[None]:
105
+ """Every process of the job the helper ``pid`` holds suspended while the block runs, and resumed after. One not yet
106
+ suspended can start another, so the job is listed again until it holds none that's new."""
107
+ job = _win32.open_job(pid)
108
+ if job is None:
109
+ yield
110
+ return
111
+ suspended, seen = [], {pid}
112
+ try:
113
+ while fresh := [app_pid for app_pid in _win32.job_pids(job) if app_pid not in seen]:
114
+ seen.update(fresh)
115
+ suspended += [app_pid for app_pid in fresh if _win32.suspend(app_pid)]
116
+ yield
117
+ finally:
118
+ for app_pid in suspended:
119
+ _win32.resume(app_pid)
120
+ _win32.close(job)
121
+
122
+
123
+ def alive(pid: int) -> bool:
124
+ """Whether a process of the job the helper ``pid`` holds runs, the helper or one of the app's; a job a stop just
125
+ ended is there, empty, until its last handle closes."""
126
+ job = _win32.open_job(pid)
127
+ if job is None:
128
+ return False
129
+ try:
130
+ return bool(_win32.job_pids(job))
131
+ finally:
132
+ _win32.close(job)
133
+
134
+
135
+ def add_launcher(name: str, folder: Path) -> Path:
136
+ """The Start Menu shortcut, named ``name``, that launches the app in ``folder`` through its own server, written
137
+ where the session user's Start Menu finds it."""
138
+ entry = Path(os.environ["APPDATA"]) / "Microsoft/Windows/Start Menu/Programs" / f"{UNSAFE.sub('_', name)}.lnk"
139
+ entry.parent.mkdir(parents=True, exist_ok=True)
140
+ subprocess.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", SHORTCUT], check=True,
141
+ capture_output=True, timeout=60, creationflags=subprocess.CREATE_NO_WINDOW,
142
+ env={**os.environ, "AGENTENV_SHORTCUT": str(entry), "AGENTENV_FOLDER": str(folder),
143
+ "AGENTENV_TARGET": str(folder / "venv" / "Scripts" / "pythonw.exe"),
144
+ "AGENTENV_ARGUMENTS": f'-m agentenv_protocol.computer.launch "{folder}"'})
145
+ return entry
146
+
147
+
148
+ async def windows() -> list[dict[str, str]]:
149
+ """The session's shown top-level windows, each as its program's file name, lowercased, its ``instance``, its window
150
+ class its ``class``, and its ``title``; a window that's hidden or cloaked, as a suspended Store app's frame is, is
151
+ left out."""
152
+ return await asyncio.to_thread(lambda: [
153
+ {"instance": window.program, "class": window.class_name, "title": window.title}
154
+ for window in _win32.windows() if window.shown])
155
+
156
+
157
+ def _app_pids(job: int, helper: int) -> list[int]:
158
+ """The processes of the job ``job`` but the helper ``helper`` that holds it."""
159
+ return [pid for pid in _win32.job_pids(job) if pid != helper]
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class Window:
164
+ """A top-level window as ``EnumWindows`` lists it: its ``handle``, the ``pid`` of its process, that process's
165
+ ``program`` file name, lowercased, its ``class_name`` and ``title``, and whether it's ``shown``."""
166
+
167
+ handle: int
168
+ pid: int
169
+ program: str
170
+ class_name: str
171
+ title: str
172
+ shown: bool
173
+
174
+
175
+ class _Win32:
176
+ """The Win32 calls this module makes, each a method, so its logic is tested apart from them."""
177
+
178
+ PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SUSPEND_RESUME, SYNCHRONIZE = 0x1000, 0x0800, 0x00100000
179
+ #: The most handles one wait takes (``MAXIMUM_WAIT_OBJECTS``).
180
+ WAIT_HANDLES = 64
181
+ DWMWA_CLOAKED = 14
182
+ #: How many process ids a job's list is read with room for, before it's read again with room for all.
183
+ PIDS = 64
184
+ #: The characters a window class's name and a program's full path take at most, with the closing NUL.
185
+ CLASS_NAME_CHARS, PATH_CHARS = 256, 32768
186
+
187
+ def __init__(self) -> None:
188
+ self._dlls: Optional[_Dlls] = None
189
+
190
+ def open_job(self, helper: int) -> Optional[int]:
191
+ """A handle to the job the helper ``helper`` holds, to query and end it, or None when it isn't there."""
192
+ return self._api().kernel32.OpenJobObjectW(_job.JOB_OBJECT_QUERY | _job.JOB_OBJECT_TERMINATE, False,
193
+ _job.job_name(helper)) or None
194
+
195
+ def job_pids(self, job: int) -> list[int]:
196
+ kernel32 = self._api().kernel32
197
+ room = self.PIDS
198
+ while True:
199
+ listed = _pid_list(room)()
200
+ if kernel32.QueryInformationJobObject(job, _job.BASIC_PROCESS_ID_LIST, ctypes.byref(listed),
201
+ ctypes.sizeof(listed), None):
202
+ return list(listed.ProcessIdList[:listed.NumberOfProcessIdsInList])
203
+ if listed.NumberOfAssignedProcesses <= room:
204
+ raise ctypes.WinError(ctypes.get_last_error())
205
+ room = listed.NumberOfAssignedProcesses
206
+
207
+ def terminate_job(self, job: int) -> None:
208
+ if not self._api().kernel32.TerminateJobObject(job, 1):
209
+ raise ctypes.WinError(ctypes.get_last_error())
210
+
211
+ def close(self, handle: int) -> None:
212
+ self._api().kernel32.CloseHandle(handle)
213
+
214
+ def open_processes(self, pids: list[int]) -> list[int]:
215
+ """Handles to wait on each process of ``pids`` that's there; one that's gone has none."""
216
+ kernel32 = self._api().kernel32
217
+ return [process for pid in pids if (process := kernel32.OpenProcess(self.SYNCHRONIZE, False, pid))]
218
+
219
+ def wait_exited(self, processes: list[int], timeout_s: float) -> None:
220
+ """Wait until each of ``processes``, handles, has exited, every thread of it ended, or ``timeout_s`` passes: a
221
+ process's own handle says it has, where its job's list says only that it's no longer in the job."""
222
+ kernel32, deadline = self._api().kernel32, time.monotonic() + timeout_s
223
+ for first in range(0, len(processes), self.WAIT_HANDLES):
224
+ some = processes[first:first + self.WAIT_HANDLES]
225
+ kernel32.WaitForMultipleObjects(len(some), (ctypes.c_void_p * len(some))(*some), True,
226
+ max(0, int((deadline - time.monotonic()) * 1000)))
227
+
228
+ def suspend(self, pid: int) -> bool:
229
+ """Whether the process ``pid`` was there to suspend, and was."""
230
+ return bool(self._on_process(pid, self.PROCESS_SUSPEND_RESUME,
231
+ lambda process: self._api().ntdll.NtSuspendProcess(process) == 0))
232
+
233
+ def resume(self, pid: int) -> None:
234
+ self._on_process(pid, self.PROCESS_SUSPEND_RESUME, lambda process: self._api().ntdll.NtResumeProcess(process))
235
+
236
+ def post_close(self, window: int) -> None:
237
+ self._api().user32.PostMessageW(window, WM_CLOSE, 0, 0)
238
+
239
+ def windows(self) -> list[Window]:
240
+ dlls = self._api()
241
+ user32, dwmapi = dlls.user32, dlls.dwmapi
242
+ handles: list[int] = []
243
+ listing = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)(
244
+ lambda window, _: handles.append(window) or True)
245
+ user32.EnumWindows(listing, None)
246
+ found = []
247
+ for handle in handles:
248
+ pid, cloaked = ctypes.c_uint32(), ctypes.c_uint32()
249
+ user32.GetWindowThreadProcessId(handle, ctypes.byref(pid))
250
+ class_name, title = ctypes.create_unicode_buffer(self.CLASS_NAME_CHARS), ctypes.create_unicode_buffer(
251
+ user32.GetWindowTextLengthW(handle) + 1)
252
+ user32.GetClassNameW(handle, class_name, len(class_name))
253
+ user32.GetWindowTextW(handle, title, len(title))
254
+ dwmapi.DwmGetWindowAttribute(handle, self.DWMWA_CLOAKED, ctypes.byref(cloaked), ctypes.sizeof(cloaked))
255
+ found.append(Window(handle, pid.value, self._program(pid.value), class_name.value, title.value,
256
+ bool(user32.IsWindowVisible(handle)) and not cloaked.value))
257
+ return found
258
+
259
+ def _program(self, pid: int) -> str:
260
+ """The file name of the program running as ``pid``, lowercased, or empty where it can't be read."""
261
+ def read(process: int) -> str:
262
+ path, size = ctypes.create_unicode_buffer(self.PATH_CHARS), ctypes.c_uint32(self.PATH_CHARS)
263
+ if not self._api().kernel32.QueryFullProcessImageNameW(process, 0, path, ctypes.byref(size)):
264
+ return ""
265
+ return path.value.rpartition("\\")[2].lower()
266
+ return self._on_process(pid, self.PROCESS_QUERY_LIMITED_INFORMATION, read) or ""
267
+
268
+ def _on_process(self, pid: int, access: int, act):
269
+ """``act`` on a handle to the process ``pid`` opened for ``access``, closed after; None where it can't be
270
+ opened, as for a process that has exited."""
271
+ kernel32 = self._api().kernel32
272
+ process = kernel32.OpenProcess(access, False, pid)
273
+ if not process:
274
+ return None
275
+ try:
276
+ return act(process)
277
+ finally:
278
+ kernel32.CloseHandle(process)
279
+
280
+ def _api(self) -> _Dlls:
281
+ """The DLLs this class calls, bound on first use, so this module imports anywhere."""
282
+ if self._dlls is None:
283
+ user32 = ctypes.WinDLL("user32", use_last_error=True)
284
+ user32.EnumWindows.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
285
+ user32.GetWindowThreadProcessId.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
286
+ user32.GetWindowTextLengthW.argtypes = user32.IsWindowVisible.argtypes = [ctypes.c_void_p]
287
+ user32.GetClassNameW.argtypes = user32.GetWindowTextW.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p,
288
+ ctypes.c_int]
289
+ user32.PostMessageW.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_size_t, ctypes.c_ssize_t]
290
+ dwmapi = ctypes.WinDLL("dwmapi")
291
+ dwmapi.DwmGetWindowAttribute.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p,
292
+ ctypes.c_uint32]
293
+ ntdll = ctypes.WinDLL("ntdll")
294
+ ntdll.NtSuspendProcess.argtypes = ntdll.NtResumeProcess.argtypes = [ctypes.c_void_p]
295
+ self._dlls = _Dlls(_job.kernel32(), user32, dwmapi, ntdll)
296
+ return self._dlls
297
+
298
+
299
+ class _Dlls(NamedTuple):
300
+ """The DLLs ``_Win32`` calls, each with the calls it makes typed: ``kernel32``'s in ``_job``, as the helper makes
301
+ some of them too."""
302
+
303
+ kernel32: ctypes.WinDLL
304
+ user32: ctypes.WinDLL
305
+ dwmapi: ctypes.WinDLL
306
+ ntdll: ctypes.WinDLL
307
+
308
+
309
+ def _pid_list(room: int) -> type[ctypes.Structure]:
310
+ """``JOBOBJECT_BASIC_PROCESS_ID_LIST`` with room for ``room`` process ids."""
311
+ class Listed(ctypes.Structure):
312
+ _fields_ = [("NumberOfAssignedProcesses", ctypes.c_uint32), ("NumberOfProcessIdsInList", ctypes.c_uint32),
313
+ ("ProcessIdList", ctypes.c_size_t * room)]
314
+ return Listed
315
+
316
+
317
+ _win32 = _Win32()
@@ -41,11 +41,14 @@ _PATHS = TypeAdapter(list[RelativePath])
41
41
  WINDOW_KEYS = frozenset({"class", "instance", "title"})
42
42
  #: The framework extension an app's own server serves: its launch as params, and its ``launch`` and ``ready`` methods.
43
43
  COMPUTER_APP = "urn:agentenv:computer-app/v1"
44
- #: Where an app's own server listens, in the app's folder, and where it says why it last failed to start.
44
+ #: Where an app's own server listens on Linux and macOS, in the app's folder.
45
45
  SOCKET = Path(".agentenv/server.sock")
46
+ #: Where an app's server on Windows writes the port it serves on and its token, in the app's folder.
47
+ ADDRESS = Path(".agentenv/server.json")
46
48
  #: 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
49
  #: opens to agents: a fresh computer starts without it, and an app that resets removes its own.
48
50
  AGENT_ACTED = Path(".agentenv/agent-acted")
51
+ #: Where an app's server says why it last failed to start, in the app's folder.
49
52
  FAILURE = Path(".agentenv/server.failure")
50
53
  #: The values of an app's build settings its env fixes in its computer's image, in the app's folder.
51
54
  BUILD_SETTINGS = Path(".agentenv/build_settings.json")
@@ -145,6 +148,20 @@ class Linux(BaseModel):
145
148
  ports: Optional[list[int]] = None
146
149
 
147
150
 
151
+ class Windows(BaseModel):
152
+ """How a Windows computer installs the app: as on Linux, with no apt; ``run`` scripts are PowerShell (``.ps1``), run
153
+ elevated as the computer is composed, and ``services`` run in the session user's sign-in."""
154
+
155
+ model_config = ConfigDict(extra="forbid")
156
+
157
+ files: LayerPath = "."
158
+ python: Optional[LayerPath] = None
159
+ downloads: Optional[list[Download]] = None
160
+ run: Optional[list[RunScript]] = None
161
+ services: list[Service] = Field(default_factory=list)
162
+ ports: Optional[list[int]] = None
163
+
164
+
148
165
  class Ready(BaseModel):
149
166
  """When the app is ready: its window shown, a port answering or a command exiting 0, within ``timeout_s``."""
150
167
 
@@ -169,7 +186,8 @@ class Launch(BaseModel):
169
186
 
170
187
  at: Literal["boot", "demand"] = "boot"
171
188
  command: list[str]
172
- #: Matches the app's window by its WM_CLASS ``class`` or ``instance`` and its ``title``, as given.
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``.
173
191
  window: Optional[dict[str, str]] = None
174
192
  ready: Optional[Ready] = None
175
193
  #: Variables set in the app's environment, over its server's.
@@ -337,6 +355,18 @@ def linux(*, files: str = ".", packages: Optional[Packages] = None, python: Opti
337
355
  launch=launch, paths=paths, skill=skill, settings=settings, build_settings=build_settings)
338
356
 
339
357
 
358
+ def windows(*, files: str = ".", python: Optional[str] = None, downloads: Optional[list[Download]] = None,
359
+ run: Optional[list[RunScript]] = None, services: Optional[list[Service]] = None,
360
+ ports: Optional[list[int]] = None, launch: Optional[Launch] = None, paths: Optional[list[str]] = None,
361
+ skill: Optional[str] = None, settings: Optional[type[BaseModel]] = None,
362
+ build_settings: Optional[type[BaseModel]] = None) -> Callable:
363
+ """``@install`` for Windows: how a Windows computer installs the app, ``Windows``'s fields, and what is its own
364
+ there."""
365
+ return install("windows", Windows(files=files, python=python, downloads=downloads, run=run,
366
+ services=services or [], ports=ports),
367
+ launch=launch, paths=paths, skill=skill, settings=settings, build_settings=build_settings)
368
+
369
+
340
370
  def install_step(scope: Literal["system", "session"] = "system", *,
341
371
  os: Union[str, list[str], None] = None) -> Callable:
342
372
  """Mark a method a computer runs as it installs the app, after its files, packages, venv and run scripts, in the