agentenv-framework-protocol 0.1.506__tar.gz → 0.1.508__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.
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/PKG-INFO +47 -1
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/README.md +44 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/pyproject.toml +5 -1
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/__init__.py +4 -2
- agentenv_framework_protocol-0.1.508/src/agentenv_protocol/computer/_desktop.py +35 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_macos.py +3 -6
- agentenv_framework_protocol-0.1.508/src/agentenv_protocol/computer/_windows.py +316 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_x11.py +7 -28
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/__init__.py +6 -2
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/environment.py +69 -11
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/readiness.py +3 -0
- agentenv_framework_protocol-0.1.508/tests/test_computer_windows_driver.py +267 -0
- agentenv_framework_protocol-0.1.508/tests/test_website_live.py +101 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/Dockerfile +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/Dockerfile.dockerignore +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/README.md +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/data/seed.json +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/env.toml +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/site.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/web/index.html +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/web/package-lock.json +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/web/package.json +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/web/src/main.js +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/web/src/style.css +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/examples/website/notes/web/vite.config.js +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/browser_cdp.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/client.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_job.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_reaping.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_session_posix.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_session_windows.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/app.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/app_data.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/app_runtime.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/apps.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/card.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/desk.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/page_fixture.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/conformance/wayland_fixture.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/driver.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/errors.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/frames.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/operations.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/platform.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/prefix.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/results.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/runtime.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/seed.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/served.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/session.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/svc.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/computer/tools.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/clock.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/conformance.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/errors.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/routes.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website/session.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website_browser/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website_browser/control.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website_browser/data.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website_browser/devtools.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website_browser/environment.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/src/agentenv_protocol/website_browser/shapes.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/probe_fake.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_browser_cdp.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_browser_data.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_browser_state.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_client.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_app_server.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_conformance_page.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_driver.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_macos.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_page.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_seed.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_served.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_session_windows.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_svc_forward.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_computer_tool_prefix.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_tool_context_injection.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_website_api.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_website_browser.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_website_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_website_environment.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_website_mcp.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/tests/test_website_notes_example.py +0 -0
- {agentenv_framework_protocol-0.1.506 → agentenv_framework_protocol-0.1.508}/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.
|
|
3
|
+
Version: 0.1.508
|
|
4
4
|
Summary: Open data-plane protocol and server SDK for agent environments.
|
|
5
5
|
Project-URL: Homepage, https://www.agentenvframework.com
|
|
6
6
|
Project-URL: Documentation, https://www.agentenvframework.com/docs
|
|
@@ -58,6 +58,8 @@ Requires-Dist: websockets>=13; extra == 'dev'
|
|
|
58
58
|
Provides-Extra: macos
|
|
59
59
|
Requires-Dist: pyobjc-framework-quartz>=11; (sys_platform == 'darwin') and extra == 'macos'
|
|
60
60
|
Requires-Dist: pyobjc-framework-screencapturekit>=11; (sys_platform == 'darwin') and extra == 'macos'
|
|
61
|
+
Provides-Extra: windows
|
|
62
|
+
Requires-Dist: mss<11,>=9; extra == 'windows'
|
|
61
63
|
Provides-Extra: x11
|
|
62
64
|
Requires-Dist: mss<11,>=9; extra == 'x11'
|
|
63
65
|
Requires-Dist: python-xlib<0.34,>=0.33; extra == 'x11'
|
|
@@ -555,6 +557,50 @@ called with the caller's role in `ToolContext`. The site's `/mcp` is one of the
|
|
|
555
557
|
browsing route (`/website/<name>/`) never reaches. `python -m agentenv_protocol.website.conformance` checks a declared
|
|
556
558
|
endpoint (`mcp`): it answers `initialize` and lists the tools the card does.
|
|
557
559
|
|
|
560
|
+
## Live websites
|
|
561
|
+
|
|
562
|
+
A live site is a real one, such as github.com, that a task puts in a multi env beside synthetic sites. Its server serves
|
|
563
|
+
none of its pages: the real site does, and the computer reaches it over its own network. The server holds what agentenv
|
|
564
|
+
knows of the site: its card, a data plane that seeds and reads the task's account through the real site's API, `ready`
|
|
565
|
+
saying the account works, and `session`, the account's cookies and local storage, which `browser_sign_in` loads into the
|
|
566
|
+
browser.
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
from pydantic import BaseModel, SecretStr
|
|
570
|
+
from agentenv_protocol.website import WebsiteEnvironment, ready_check, session_state, website
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@website(domains=["github.com"], live=True, allow_hosts=["*.githubassets.com", "api.github.com"])
|
|
574
|
+
class GitHub(WebsiteEnvironment):
|
|
575
|
+
class Settings(BaseModel):
|
|
576
|
+
token: SecretStr # in env.toml as a secret:KEY or env:NAME reference
|
|
577
|
+
|
|
578
|
+
@ready_check
|
|
579
|
+
def account_works(self) -> bool:
|
|
580
|
+
... # the API answers for self.settings.token
|
|
581
|
+
|
|
582
|
+
@session_state
|
|
583
|
+
def account(self, user: str | None) -> dict:
|
|
584
|
+
... # {"cookies": [...], "local_storage": [...]} that sign a browser in
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
- **Its names:** `domains` are the real site's, where start tabs may open and `browser_sign_in` sets the session's
|
|
588
|
+
cookies. `allow_hosts` are the other hosts its pages reach, such as its assets and API. A computer whose network is an
|
|
589
|
+
allowlist may reach both; one that allows every host is left as it is. A name a synthetic site of the run also claims
|
|
590
|
+
is that site's.
|
|
591
|
+
- **Its card:** `website/v1` says `live` (`WEBSITE_LIVE_PARAM`) and `allow_hosts` (`WEBSITE_ALLOW_HOSTS_PARAM`) beside
|
|
592
|
+
`domains`, and names no routes. The server serves no `/api/session` page route. `@website` refuses a live site
|
|
593
|
+
with no domains or a static folder, and `allow_hosts` on a site that isn't live.
|
|
594
|
+
- **Its session:** the method marked `@session_state` answers `session {user}`. A live site must mark one, since only
|
|
595
|
+
the real site's cookies sign a browser in to it. Each cookie carries no domain and a `max_age`, as a synthetic
|
|
596
|
+
site's does.
|
|
597
|
+
- **Settings:** any site may declare `Settings`. A deploy hands them to its server in `WEBSITE_SETTINGS_ENV`, a JSON
|
|
598
|
+
object, and the site reads them as `self.settings`, unless it has a member of that name of its own. A `SecretStr`
|
|
599
|
+
field's value in env.toml is a `secret:` or `env:` reference, which agentenv resolves only as it writes the server's
|
|
600
|
+
container, and every record shows it as withheld.
|
|
601
|
+
- **The run:** a run that deploys a live site is marked live. The deploy record says `live`, and the run's metadata lists
|
|
602
|
+
its live sites with `reproducible: false`, because the real site may change between runs.
|
|
603
|
+
|
|
558
604
|
## Computer drivers
|
|
559
605
|
|
|
560
606
|
A computer driver is what runs inside a computer an agent uses through screenshots and input. Write
|
|
@@ -490,6 +490,50 @@ called with the caller's role in `ToolContext`. The site's `/mcp` is one of the
|
|
|
490
490
|
browsing route (`/website/<name>/`) never reaches. `python -m agentenv_protocol.website.conformance` checks a declared
|
|
491
491
|
endpoint (`mcp`): it answers `initialize` and lists the tools the card does.
|
|
492
492
|
|
|
493
|
+
## Live websites
|
|
494
|
+
|
|
495
|
+
A live site is a real one, such as github.com, that a task puts in a multi env beside synthetic sites. Its server serves
|
|
496
|
+
none of its pages: the real site does, and the computer reaches it over its own network. The server holds what agentenv
|
|
497
|
+
knows of the site: its card, a data plane that seeds and reads the task's account through the real site's API, `ready`
|
|
498
|
+
saying the account works, and `session`, the account's cookies and local storage, which `browser_sign_in` loads into the
|
|
499
|
+
browser.
|
|
500
|
+
|
|
501
|
+
```python
|
|
502
|
+
from pydantic import BaseModel, SecretStr
|
|
503
|
+
from agentenv_protocol.website import WebsiteEnvironment, ready_check, session_state, website
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@website(domains=["github.com"], live=True, allow_hosts=["*.githubassets.com", "api.github.com"])
|
|
507
|
+
class GitHub(WebsiteEnvironment):
|
|
508
|
+
class Settings(BaseModel):
|
|
509
|
+
token: SecretStr # in env.toml as a secret:KEY or env:NAME reference
|
|
510
|
+
|
|
511
|
+
@ready_check
|
|
512
|
+
def account_works(self) -> bool:
|
|
513
|
+
... # the API answers for self.settings.token
|
|
514
|
+
|
|
515
|
+
@session_state
|
|
516
|
+
def account(self, user: str | None) -> dict:
|
|
517
|
+
... # {"cookies": [...], "local_storage": [...]} that sign a browser in
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
- **Its names:** `domains` are the real site's, where start tabs may open and `browser_sign_in` sets the session's
|
|
521
|
+
cookies. `allow_hosts` are the other hosts its pages reach, such as its assets and API. A computer whose network is an
|
|
522
|
+
allowlist may reach both; one that allows every host is left as it is. A name a synthetic site of the run also claims
|
|
523
|
+
is that site's.
|
|
524
|
+
- **Its card:** `website/v1` says `live` (`WEBSITE_LIVE_PARAM`) and `allow_hosts` (`WEBSITE_ALLOW_HOSTS_PARAM`) beside
|
|
525
|
+
`domains`, and names no routes. The server serves no `/api/session` page route. `@website` refuses a live site
|
|
526
|
+
with no domains or a static folder, and `allow_hosts` on a site that isn't live.
|
|
527
|
+
- **Its session:** the method marked `@session_state` answers `session {user}`. A live site must mark one, since only
|
|
528
|
+
the real site's cookies sign a browser in to it. Each cookie carries no domain and a `max_age`, as a synthetic
|
|
529
|
+
site's does.
|
|
530
|
+
- **Settings:** any site may declare `Settings`. A deploy hands them to its server in `WEBSITE_SETTINGS_ENV`, a JSON
|
|
531
|
+
object, and the site reads them as `self.settings`, unless it has a member of that name of its own. A `SecretStr`
|
|
532
|
+
field's value in env.toml is a `secret:` or `env:` reference, which agentenv resolves only as it writes the server's
|
|
533
|
+
container, and every record shows it as withheld.
|
|
534
|
+
- **The run:** a run that deploys a live site is marked live. The deploy record says `live`, and the run's metadata lists
|
|
535
|
+
its live sites with `reproducible: false`, because the real site may change between runs.
|
|
536
|
+
|
|
493
537
|
## Computer drivers
|
|
494
538
|
|
|
495
539
|
A computer driver is what runs inside a computer an agent uses through screenshots and input. Write
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentenv-framework-protocol"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.508"
|
|
4
4
|
description = "Open data-plane protocol and server SDK for agent environments."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -61,6 +61,10 @@ x11 = [
|
|
|
61
61
|
"mss>=9,<11",
|
|
62
62
|
"python-xlib>=0.33,<0.34",
|
|
63
63
|
]
|
|
64
|
+
# A Windows driver on WindowsDriver: mss captures the desktop and SendInput, through ctypes, sends its input.
|
|
65
|
+
windows = [
|
|
66
|
+
"mss>=9,<11",
|
|
67
|
+
]
|
|
64
68
|
dev = [
|
|
65
69
|
"a2a-sdk[http-server]>=0.3.26,<0.4",
|
|
66
70
|
"python-multipart>=0.0.13",
|
|
@@ -100,11 +100,12 @@ if TYPE_CHECKING:
|
|
|
100
100
|
)
|
|
101
101
|
from .driver import DEFAULT_IDENTITY, ComputerDriverEnvironment, DriverDeclaration, computer_driver
|
|
102
102
|
from ._macos import MacOSDriver
|
|
103
|
+
from ._windows import WindowsDriver
|
|
103
104
|
from ._x11 import X11Driver
|
|
104
105
|
from .recording import RecordedFrame, RecordedSegment, RecordHints, Recording, frames_in_thread
|
|
105
106
|
|
|
106
107
|
# The names whose modules need the computer extra (mcp, av and Pillow), by module; MacOSDriver's, the macos extra too,
|
|
107
|
-
# on first use of the Mac,
|
|
108
|
+
# on first use of the Mac, X11Driver's, the x11 extra, and WindowsDriver's, the windows extra.
|
|
108
109
|
_ON_USE = {
|
|
109
110
|
"driver": ("ComputerDriverEnvironment", "computer_driver", "DEFAULT_IDENTITY", "DriverDeclaration"),
|
|
110
111
|
"card": ("COMPUTER", "COMPUTER_READY", "COMPUTER_DISPLAY", "COMPUTER_SPACES", "COMPUTER_POINTER",
|
|
@@ -112,6 +113,7 @@ _ON_USE = {
|
|
|
112
113
|
"recording": ("Recording", "RecordHints", "RecordedFrame", "RecordedSegment", "frames_in_thread"),
|
|
113
114
|
"_macos": ("MacOSDriver",),
|
|
114
115
|
"_x11": ("X11Driver",),
|
|
116
|
+
"_windows": ("WindowsDriver",),
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
|
|
@@ -124,7 +126,7 @@ def __getattr__(name: str) -> Any:
|
|
|
124
126
|
|
|
125
127
|
__all__ = [
|
|
126
128
|
"ComputerDriverEnvironment", "computer_driver", "DEFAULT_IDENTITY", "DriverDeclaration", "MacOSDriver",
|
|
127
|
-
"X11Driver",
|
|
129
|
+
"X11Driver", "WindowsDriver",
|
|
128
130
|
"ComputerAppEnvironment", "computer_app", "install", "linux", "windows", "install_step", "ready_check", "Linux",
|
|
129
131
|
"Windows", "Service",
|
|
130
132
|
"Launch", "Ready", "Packages", "AptSource", "Download", "RunScript",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""What the drivers on an OS's own input share: the pacing of clicks and drags, how many presses and points they take,
|
|
2
|
+
a drag's path, and a capture of every monitor through mss, whose class each driver passes from its own import."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .errors import DeviceUnavailable
|
|
9
|
+
from .operations import Image
|
|
10
|
+
|
|
11
|
+
MAX_CLICK_COUNT = 3
|
|
12
|
+
MAX_DRAG_POINTS = 50
|
|
13
|
+
#: The pause between the presses of a double or triple click, in seconds.
|
|
14
|
+
CLICK_GAP_S = 0.04
|
|
15
|
+
#: How far apart a drag's pointer moves are, in pixels.
|
|
16
|
+
DRAG_STEP_PX = 20
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def drag_steps(path: list) -> list[tuple[float, float]]:
|
|
20
|
+
"""The points a drag moves through after its start, about every DRAG_STEP_PX pixels."""
|
|
21
|
+
steps = []
|
|
22
|
+
for (x0, y0), (x1, y1) in zip(path, path[1:]):
|
|
23
|
+
n = max(2, int(math.hypot(x1 - x0, y1 - y0) / DRAG_STEP_PX))
|
|
24
|
+
steps += [(x0 + (x1 - x0) * k / n, y0 + (y1 - y0) * k / n) for k in range(1, n + 1)]
|
|
25
|
+
return steps
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def mss_capture(mss_class: Any) -> Image:
|
|
29
|
+
"""Every monitor, as mss's first monitor spans them, in BGRA."""
|
|
30
|
+
try:
|
|
31
|
+
with mss_class() as m:
|
|
32
|
+
shot = m.grab(m.monitors[0])
|
|
33
|
+
return Image(shot.width, shot.height, bytes(shot.bgra), "BGRA")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
raise DeviceUnavailable(f"capture failed: {type(e).__name__}: {e}") from None
|
|
@@ -14,6 +14,7 @@ import time
|
|
|
14
14
|
from contextlib import contextmanager
|
|
15
15
|
from typing import Any, Callable, Iterator
|
|
16
16
|
|
|
17
|
+
from ._desktop import CLICK_GAP_S, DRAG_STEP_PX, MAX_CLICK_COUNT, MAX_DRAG_POINTS
|
|
17
18
|
from .driver import ComputerDriverEnvironment
|
|
18
19
|
from .errors import DeliveryFailed, DeviceUnavailable, Unsupported
|
|
19
20
|
from .keys import PRINTABLE_KEYS
|
|
@@ -59,10 +60,6 @@ MOVED = 5
|
|
|
59
60
|
SETTLE_S = 0.2
|
|
60
61
|
#: How long a click holds its button down, as a hand does: a press released at once is lost now and then.
|
|
61
62
|
CLICK_HOLD_S = 0.03
|
|
62
|
-
#: The pause between the presses of a double or triple click.
|
|
63
|
-
CLICK_GAP_S = 0.04
|
|
64
|
-
#: How far apart a drag's pointer moves are, in pixels.
|
|
65
|
-
DRAG_STEP_PX = 20
|
|
66
63
|
#: The pause between a scroll's wheel notches, as a wheel turned by hand has: notches posted back to back reach an app
|
|
67
64
|
#: as fewer.
|
|
68
65
|
SCROLL_GAP_S = 0.05
|
|
@@ -81,8 +78,8 @@ class MacOSDriver(ComputerDriverEnvironment):
|
|
|
81
78
|
released."""
|
|
82
79
|
|
|
83
80
|
buttons = ("left", "right", "middle")
|
|
84
|
-
max_click_count =
|
|
85
|
-
max_drag_points =
|
|
81
|
+
max_click_count = MAX_CLICK_COUNT
|
|
82
|
+
max_drag_points = MAX_DRAG_POINTS
|
|
86
83
|
keys = (*MODIFIERS, *NAMED, *PRINTABLE_KEYS)
|
|
87
84
|
|
|
88
85
|
def __init__(self, settings: ComputerSettings | None = None, platform: Platform | None = None) -> None:
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""computer/v1 on a Windows desktop, for a driver whose software serves tools of its own, as agent-desktop's does: mss
|
|
2
|
+
captures the screen and SendInput sends the input, in the session the driver runs in. The process is aware of each
|
|
3
|
+
monitor's DPI, so a point is a pixel of the capture, which spans every monitor. A recording polls those captures, as
|
|
4
|
+
the base class does for a driver with no stream of its own. A driver subclasses ``WindowsDriver``, declares itself with
|
|
5
|
+
``computer_driver``, and serves its own tools beside these. Needs the ``windows`` extra: mss."""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import contextlib
|
|
10
|
+
import ctypes
|
|
11
|
+
import functools
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
import mss
|
|
15
|
+
|
|
16
|
+
from ._desktop import CLICK_GAP_S, MAX_CLICK_COUNT, MAX_DRAG_POINTS, drag_steps, mss_capture
|
|
17
|
+
from .driver import ComputerDriverEnvironment
|
|
18
|
+
from .errors import DeliveryFailed, DeviceUnavailable, Unsupported
|
|
19
|
+
from .operations import (Display, Image, ReadyCheck, capture, click, display, drag, key_down, key_up, pointer_down,
|
|
20
|
+
pointer_move, pointer_position, pointer_up, press, scroll, type_text)
|
|
21
|
+
from .platform import Platform
|
|
22
|
+
from .settings import ComputerSettings
|
|
23
|
+
|
|
24
|
+
INPUT_MOUSE, INPUT_KEYBOARD = 0, 1
|
|
25
|
+
MOVE, ABSOLUTE, VIRTUAL_DESK = 0x0001, 0x8000, 0x4000
|
|
26
|
+
WHEEL, HWHEEL, WHEEL_DELTA = 0x0800, 0x1000, 120
|
|
27
|
+
#: Each button's press and release flags, and the X button it is, for the two that are one.
|
|
28
|
+
BUTTONS = {"left": (0x0002, 0x0004, 0), "right": (0x0008, 0x0010, 0), "middle": (0x0020, 0x0040, 0),
|
|
29
|
+
"back": (0x0080, 0x0100, 1), "forward": (0x0080, 0x0100, 2)}
|
|
30
|
+
EXTENDED, KEY_UP, UNICODE = 0x0001, 0x0002, 0x0004
|
|
31
|
+
#: W3C key values and their virtual keys; any other single character is the key its keyboard layout gives it.
|
|
32
|
+
VIRTUAL_KEYS = {
|
|
33
|
+
"Enter": 0x0D, "Tab": 0x09, "Escape": 0x1B, "Backspace": 0x08, "Delete": 0x2E, "ArrowUp": 0x26, "ArrowDown": 0x28,
|
|
34
|
+
"ArrowLeft": 0x25, "ArrowRight": 0x27, "Home": 0x24, "End": 0x23, "PageUp": 0x21, "PageDown": 0x22, "Insert": 0x2D,
|
|
35
|
+
"Control": 0xA2, "Shift": 0xA0, "Alt": 0xA4, "Meta": 0x5B, "Super": 0x5B, "CapsLock": 0x14, "ContextMenu": 0x5D,
|
|
36
|
+
"PrintScreen": 0x2C, "Pause": 0x13, " ": 0x20, **{f"F{n}": 0x6F + n for n in range(1, 25)},
|
|
37
|
+
}
|
|
38
|
+
#: The keys a keyboard sends with the extended flag, without which Windows reads the arrows as the keypad's.
|
|
39
|
+
EXTENDED_KEYS = {0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x2C, 0x2D, 0x2E, 0x5B, 0x5D}
|
|
40
|
+
#: The modifiers VkKeyScanW says a character needs, as the bits of its high byte.
|
|
41
|
+
SHIFT_STATE = ((1, 0xA0), (2, 0xA2), (4, 0xA4))
|
|
42
|
+
TYPED = {"\n": 0x0D, "\t": 0x09}
|
|
43
|
+
#: SetProcessDpiAwarenessContext's per-monitor (v2) awareness.
|
|
44
|
+
PER_MONITOR_AWARE_V2 = -4
|
|
45
|
+
#: GetSystemMetrics' indexes of the virtual screen's left, top, width and height.
|
|
46
|
+
VIRTUAL_SCREEN = (76, 77, 78, 79)
|
|
47
|
+
DESKTOP_SWITCHDESKTOP = 0x0100
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _MouseInput(ctypes.Structure):
|
|
51
|
+
_fields_ = [("dx", ctypes.c_long), ("dy", ctypes.c_long), ("mouseData", ctypes.c_ulong),
|
|
52
|
+
("dwFlags", ctypes.c_ulong), ("time", ctypes.c_ulong), ("dwExtraInfo", ctypes.c_size_t)]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _KeybdInput(ctypes.Structure):
|
|
56
|
+
_fields_ = [("wVk", ctypes.c_ushort), ("wScan", ctypes.c_ushort), ("dwFlags", ctypes.c_ulong),
|
|
57
|
+
("time", ctypes.c_ulong), ("dwExtraInfo", ctypes.c_size_t)]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _HardwareInput(ctypes.Structure):
|
|
61
|
+
_fields_ = [("uMsg", ctypes.c_ulong), ("wParamL", ctypes.c_ushort), ("wParamH", ctypes.c_ushort)]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _Event(ctypes.Union):
|
|
65
|
+
_fields_ = [("mi", _MouseInput), ("ki", _KeybdInput), ("hi", _HardwareInput)]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class _Input(ctypes.Structure):
|
|
69
|
+
_fields_ = [("type", ctypes.c_ulong), ("u", _Event)]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _Point(ctypes.Structure):
|
|
73
|
+
_fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@functools.cache
|
|
77
|
+
def _user32():
|
|
78
|
+
"""Windows' user32, typed, with the per-monitor DPI awareness set once so that points and the capture's pixels
|
|
79
|
+
agree."""
|
|
80
|
+
user32 = ctypes.WinDLL("user32", use_last_error=True)
|
|
81
|
+
user32.SendInput.argtypes = (ctypes.c_uint, ctypes.POINTER(_Input), ctypes.c_int)
|
|
82
|
+
user32.SendInput.restype = ctypes.c_uint
|
|
83
|
+
user32.VkKeyScanW.argtypes = (ctypes.c_wchar,)
|
|
84
|
+
user32.VkKeyScanW.restype = ctypes.c_short
|
|
85
|
+
user32.MapVirtualKeyW.argtypes = (ctypes.c_uint, ctypes.c_uint)
|
|
86
|
+
user32.MapVirtualKeyW.restype = ctypes.c_uint
|
|
87
|
+
user32.OpenInputDesktop.argtypes = (ctypes.c_ulong, ctypes.c_bool, ctypes.c_ulong)
|
|
88
|
+
user32.OpenInputDesktop.restype = ctypes.c_void_p
|
|
89
|
+
user32.CloseDesktop.argtypes = (ctypes.c_void_p,)
|
|
90
|
+
user32.GetCursorPos.argtypes = (ctypes.POINTER(_Point),)
|
|
91
|
+
with contextlib.suppress(AttributeError):
|
|
92
|
+
user32.SetProcessDpiAwarenessContext(ctypes.c_void_p(PER_MONITOR_AWARE_V2))
|
|
93
|
+
return user32
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class WindowsDriver(ComputerDriverEnvironment):
|
|
97
|
+
"""The computer/v1 operations, the pointer and keyboard groups among them, on a Windows desktop."""
|
|
98
|
+
|
|
99
|
+
buttons = ("left", "right", "middle", "back", "forward")
|
|
100
|
+
max_click_count = MAX_CLICK_COUNT
|
|
101
|
+
max_drag_points = MAX_DRAG_POINTS
|
|
102
|
+
|
|
103
|
+
def __init__(self, settings: ComputerSettings | None = None, platform: Platform | None = None) -> None:
|
|
104
|
+
super().__init__(settings, platform)
|
|
105
|
+
#: The virtual keys each key held by @key_down pressed, which @key_up releases.
|
|
106
|
+
self._held: dict[str, list[int]] = {}
|
|
107
|
+
|
|
108
|
+
@display
|
|
109
|
+
async def screen(self) -> Display:
|
|
110
|
+
_, _, width, height = _virtual_screen()
|
|
111
|
+
return Display(width, height, 1.0)
|
|
112
|
+
|
|
113
|
+
@capture
|
|
114
|
+
async def grab(self) -> Image:
|
|
115
|
+
return await asyncio.to_thread(mss_capture, mss.MSS)
|
|
116
|
+
|
|
117
|
+
async def ready_checks(self) -> list[ReadyCheck]:
|
|
118
|
+
user32 = _user32()
|
|
119
|
+
desktop = user32.OpenInputDesktop(0, False, DESKTOP_SWITCHDESKTOP)
|
|
120
|
+
if not desktop:
|
|
121
|
+
return [ReadyCheck("input_desktop", False, f"the input desktop isn't this session's: {_last_error()}")]
|
|
122
|
+
user32.CloseDesktop(desktop)
|
|
123
|
+
return [ReadyCheck("input_desktop", True, "the input desktop is this session's")]
|
|
124
|
+
|
|
125
|
+
@click
|
|
126
|
+
async def click_at(self, x, y, button, count, modifiers) -> None:
|
|
127
|
+
held = self._pressing(modifiers)
|
|
128
|
+
|
|
129
|
+
def act():
|
|
130
|
+
_send([_key(vk, True) for vk in held] + [_move(x, y)])
|
|
131
|
+
for i in range(count):
|
|
132
|
+
_send([_button(button, True), _button(button, False)])
|
|
133
|
+
if i + 1 < count:
|
|
134
|
+
time.sleep(CLICK_GAP_S)
|
|
135
|
+
_send([_key(vk, False) for vk in reversed(held)])
|
|
136
|
+
await asyncio.to_thread(act)
|
|
137
|
+
|
|
138
|
+
@drag
|
|
139
|
+
async def drag_along(self, path, button, duration_ms, modifiers) -> None:
|
|
140
|
+
held = self._pressing(modifiers)
|
|
141
|
+
steps = drag_steps(path)
|
|
142
|
+
pause = duration_ms / 1000 / len(steps)
|
|
143
|
+
finish = [_button(button, False)] + [_key(vk, False) for vk in reversed(held)]
|
|
144
|
+
try:
|
|
145
|
+
await asyncio.to_thread(_send, [_key(vk, True) for vk in held] + [_move(*path[0]), _button(button, True)])
|
|
146
|
+
for px, py in steps:
|
|
147
|
+
await asyncio.to_thread(_send, [_move(px, py)])
|
|
148
|
+
await asyncio.sleep(pause)
|
|
149
|
+
except BaseException:
|
|
150
|
+
with contextlib.suppress(Exception):
|
|
151
|
+
await asyncio.to_thread(_send, finish)
|
|
152
|
+
raise
|
|
153
|
+
await asyncio.to_thread(_send, finish)
|
|
154
|
+
|
|
155
|
+
@scroll
|
|
156
|
+
async def scroll_at(self, x, y, direction, amount, modifiers) -> None:
|
|
157
|
+
held = self._pressing(modifiers)
|
|
158
|
+
await asyncio.to_thread(_send, [_key(vk, True) for vk in held] + [_move(x, y)] + [_wheel(direction)] * amount
|
|
159
|
+
+ [_key(vk, False) for vk in reversed(held)])
|
|
160
|
+
|
|
161
|
+
@pointer_move
|
|
162
|
+
async def move(self, x, y) -> None:
|
|
163
|
+
await asyncio.to_thread(_send, [_move(x, y)])
|
|
164
|
+
|
|
165
|
+
@pointer_down
|
|
166
|
+
async def down(self, button) -> None:
|
|
167
|
+
await asyncio.to_thread(_send, [_button(button, True)])
|
|
168
|
+
|
|
169
|
+
@pointer_up
|
|
170
|
+
async def up(self, button) -> None:
|
|
171
|
+
await asyncio.to_thread(_send, [_button(button, False)])
|
|
172
|
+
|
|
173
|
+
@pointer_position
|
|
174
|
+
async def where(self) -> tuple[int, int]:
|
|
175
|
+
point = _Point()
|
|
176
|
+
if not _user32().GetCursorPos(ctypes.byref(point)):
|
|
177
|
+
raise DeviceUnavailable(f"the pointer can't be read: {_last_error()}")
|
|
178
|
+
left, top, _, _ = _virtual_screen()
|
|
179
|
+
return point.x - left, point.y - top
|
|
180
|
+
|
|
181
|
+
@type_text
|
|
182
|
+
async def type_in(self, text) -> None:
|
|
183
|
+
"""Each character as its key on the keyboard layout, with the modifiers it needs that no held key holds, so an
|
|
184
|
+
app sees the keys; a character off the layout as itself."""
|
|
185
|
+
hold = self.settings.tap_hold_ms / 1000
|
|
186
|
+
for ch in text:
|
|
187
|
+
vks = [TYPED[ch]] if ch in TYPED else _layout_keys(ch)
|
|
188
|
+
if vks is None:
|
|
189
|
+
downs, ups = _unicode(ch)
|
|
190
|
+
else:
|
|
191
|
+
pressed = [vk for vk in vks if vk not in self._down()]
|
|
192
|
+
downs, ups = [_key(vk, True) for vk in pressed], [_key(vk, False) for vk in reversed(pressed)]
|
|
193
|
+
await asyncio.to_thread(_tap, downs, ups, hold)
|
|
194
|
+
await asyncio.sleep(self.settings.type_delay_ms / 1000)
|
|
195
|
+
|
|
196
|
+
@press
|
|
197
|
+
async def press_keys(self, keys) -> None:
|
|
198
|
+
pressed = self._pressing(keys)
|
|
199
|
+
await asyncio.to_thread(_tap, [_key(vk, True) for vk in pressed], [_key(vk, False) for vk in reversed(pressed)],
|
|
200
|
+
self.settings.tap_hold_ms / 1000)
|
|
201
|
+
|
|
202
|
+
@key_down
|
|
203
|
+
async def keys_down(self, keys) -> None:
|
|
204
|
+
"""Press what the keys need that isn't down already; for each key, the virtual keys this pressed, so that
|
|
205
|
+
@key_up releases only those. Every key is looked up before the first press."""
|
|
206
|
+
down = self._down()
|
|
207
|
+
planned = {}
|
|
208
|
+
for key in dict.fromkeys(keys):
|
|
209
|
+
planned[key] = [vk for vk in _virtual_keys(key) if vk not in down]
|
|
210
|
+
down.update(planned[key])
|
|
211
|
+
await asyncio.to_thread(_send, [_key(vk, True) for vks in planned.values() for vk in vks])
|
|
212
|
+
for key, vks in planned.items():
|
|
213
|
+
self._held.setdefault(key, []).extend(vks)
|
|
214
|
+
|
|
215
|
+
@key_up
|
|
216
|
+
async def keys_up(self, keys) -> None:
|
|
217
|
+
"""Release what each key's @key_down pressed, nothing for one whose keys were down already, and a key none
|
|
218
|
+
holds as itself; the keys stay held until Windows takes the releases, so a refused call can be made again."""
|
|
219
|
+
released = [vk for key in dict.fromkeys(keys)
|
|
220
|
+
for vk in reversed(self._held[key] if key in self._held else _virtual_keys(key)[-1:])]
|
|
221
|
+
await asyncio.to_thread(_send, [_key(vk, False) for vk in released])
|
|
222
|
+
for key in keys:
|
|
223
|
+
self._held.pop(key, None)
|
|
224
|
+
|
|
225
|
+
def _down(self) -> set[int]:
|
|
226
|
+
return {vk for vks in self._held.values() for vk in vks}
|
|
227
|
+
|
|
228
|
+
def _pressing(self, keys) -> list[int]:
|
|
229
|
+
"""The virtual keys that hold ``keys`` down together, in order, each once, but those held keys hold already."""
|
|
230
|
+
down = self._down()
|
|
231
|
+
return [vk for vk in dict.fromkeys(vk for key in keys for vk in _virtual_keys(key)) if vk not in down]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _send(events: list[_Input]) -> None:
|
|
235
|
+
"""``events`` through SendInput, refused when Windows takes fewer than all, as it does when the input desktop isn't
|
|
236
|
+
this session's or a window of higher integrity is in front."""
|
|
237
|
+
if not events:
|
|
238
|
+
return
|
|
239
|
+
sent = _user32().SendInput(len(events), (_Input * len(events))(*events), ctypes.sizeof(_Input))
|
|
240
|
+
if sent != len(events):
|
|
241
|
+
raise DeliveryFailed(f"Windows took {sent} of {len(events)} input events: {_last_error()}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _tap(downs: list[_Input], ups: list[_Input], hold_s: float) -> None:
|
|
245
|
+
_send(downs)
|
|
246
|
+
time.sleep(hold_s)
|
|
247
|
+
_send(ups)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _virtual_screen() -> tuple[int, int, int, int]:
|
|
251
|
+
user32 = _user32()
|
|
252
|
+
return tuple(user32.GetSystemMetrics(i) for i in VIRTUAL_SCREEN)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _move(x: float, y: float) -> _Input:
|
|
256
|
+
"""A move to the pixel (x, y) of the capture, aimed at its centre in the virtual screen's 0 to 65535 coordinates,
|
|
257
|
+
so that Windows' rounding lands on it."""
|
|
258
|
+
_, _, width, height = _virtual_screen()
|
|
259
|
+
dx = min(65535, round((int(x) + 0.5) * 65536 / width))
|
|
260
|
+
dy = min(65535, round((int(y) + 0.5) * 65536 / height))
|
|
261
|
+
return _Input(INPUT_MOUSE, _Event(mi=_MouseInput(dx, dy, 0, MOVE | ABSOLUTE | VIRTUAL_DESK, 0, 0)))
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _button(button: str, down: bool) -> _Input:
|
|
265
|
+
press_flag, release_flag, which = BUTTONS[button]
|
|
266
|
+
return _Input(INPUT_MOUSE, _Event(mi=_MouseInput(0, 0, which, press_flag if down else release_flag, 0, 0)))
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _wheel(direction: str) -> _Input:
|
|
270
|
+
"""One notch: up and right turn the wheel forward, down and left back."""
|
|
271
|
+
flag = HWHEEL if direction in ("left", "right") else WHEEL
|
|
272
|
+
notch = WHEEL_DELTA if direction in ("up", "right") else -WHEEL_DELTA
|
|
273
|
+
return _Input(INPUT_MOUSE, _Event(mi=_MouseInput(0, 0, ctypes.c_ulong(notch).value, flag, 0, 0)))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _key(vk: int, down: bool) -> _Input:
|
|
277
|
+
"""A virtual key with its scan code, which apps that read the physical key, as Chromium's ``code`` does, need."""
|
|
278
|
+
flags = (EXTENDED if vk in EXTENDED_KEYS else 0) | (0 if down else KEY_UP)
|
|
279
|
+
return _Input(INPUT_KEYBOARD, _Event(ki=_KeybdInput(vk, _user32().MapVirtualKeyW(vk, 0), flags, 0, 0)))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _unicode(ch: str) -> tuple[list[_Input], list[_Input]]:
|
|
283
|
+
"""A character typed as itself, whatever the keyboard layout: a key for each of its UTF-16 units, two for one
|
|
284
|
+
beyond the BMP."""
|
|
285
|
+
data = ch.encode("utf-16-le")
|
|
286
|
+
units = [int.from_bytes(data[i:i + 2], "little") for i in range(0, len(data), 2)]
|
|
287
|
+
return [_unit(u, 0) for u in units], [_unit(u, KEY_UP) for u in units]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _unit(unit: int, flags: int) -> _Input:
|
|
291
|
+
return _Input(INPUT_KEYBOARD, _Event(ki=_KeybdInput(0, unit, UNICODE | flags, 0, 0)))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _virtual_keys(key: str) -> list[int]:
|
|
295
|
+
"""The virtual keys that make ``key``: a named key's, or a character's on the keyboard layout with the modifiers
|
|
296
|
+
it needs first."""
|
|
297
|
+
if key in VIRTUAL_KEYS:
|
|
298
|
+
return [VIRTUAL_KEYS[key]]
|
|
299
|
+
if len(key) == 1:
|
|
300
|
+
if (vks := _layout_keys(key)) is None:
|
|
301
|
+
raise Unsupported(f"{key!r} has no key on this keyboard layout; type it as text instead")
|
|
302
|
+
return vks
|
|
303
|
+
raise Unsupported(f"this driver has no Windows virtual key for {key!r}")
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _layout_keys(ch: str) -> list[int] | None:
|
|
307
|
+
"""The modifiers and key that type ``ch`` on the keyboard layout, None for a character it has no key for, as one
|
|
308
|
+
beyond the BMP, which VkKeyScanW can't take."""
|
|
309
|
+
scan = _user32().VkKeyScanW(ch) if ord(ch) <= 0xFFFF else -1
|
|
310
|
+
return None if scan == -1 else [vk for bit, vk in SHIFT_STATE if scan >> 8 & bit] + [scan & 0xFF]
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _last_error() -> str:
|
|
314
|
+
return ctypes.WinError(ctypes.get_last_error()).strerror
|
|
315
|
+
|
|
316
|
+
|
|
@@ -8,7 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import contextlib
|
|
11
|
-
import math
|
|
12
11
|
import os
|
|
13
12
|
import threading
|
|
14
13
|
import time
|
|
@@ -23,6 +22,7 @@ from Xlib import display as xdisplay
|
|
|
23
22
|
from Xlib.error import ConnectionClosedError, DisplayConnectionError
|
|
24
23
|
from Xlib.ext import xtest
|
|
25
24
|
|
|
25
|
+
from ._desktop import CLICK_GAP_S, MAX_CLICK_COUNT, MAX_DRAG_POINTS, drag_steps, mss_capture
|
|
26
26
|
from .driver import ComputerDriverEnvironment
|
|
27
27
|
from .errors import DeliveryFailed, DeviceUnavailable, Unsupported
|
|
28
28
|
from .operations import (Display, Image, ReadyCheck, capture, click, display, drag, key_down, key_up, pointer_down,
|
|
@@ -44,13 +44,9 @@ KEYSYMS = {
|
|
|
44
44
|
"PrintScreen": "Print", "Pause": "Pause", " ": "space", **{f"F{n}": f"F{n}" for n in range(1, 25)},
|
|
45
45
|
}
|
|
46
46
|
TYPED = {"\n": "Return", "\t": "Tab"}
|
|
47
|
-
#: The pause between the presses of a double or triple click, in seconds.
|
|
48
|
-
CLICK_GAP_S = 0.04
|
|
49
47
|
#: How long a button's release may take to reach the app, and how often the driver looks.
|
|
50
48
|
RELEASE_WAIT_S = 5.0
|
|
51
49
|
RELEASE_POLL_S = 0.005
|
|
52
|
-
#: How far apart a drag's pointer moves are, in pixels.
|
|
53
|
-
DRAG_STEP_PX = 20
|
|
54
50
|
DROPPED = (ConnectionClosedError, DisplayConnectionError, OSError)
|
|
55
51
|
|
|
56
52
|
|
|
@@ -58,8 +54,8 @@ class X11Driver(ComputerDriverEnvironment):
|
|
|
58
54
|
"""The computer/v1 operations, the pointer and keyboard groups among them, on an X11 desktop."""
|
|
59
55
|
|
|
60
56
|
buttons = ("left", "right", "middle", "back", "forward")
|
|
61
|
-
max_click_count =
|
|
62
|
-
max_drag_points =
|
|
57
|
+
max_click_count = MAX_CLICK_COUNT
|
|
58
|
+
max_drag_points = MAX_DRAG_POINTS
|
|
63
59
|
recording = Recording(max_fps=30)
|
|
64
60
|
|
|
65
61
|
def __init__(self, settings: ComputerSettings | None = None, platform: Platform | None = None) -> None:
|
|
@@ -78,7 +74,7 @@ class X11Driver(ComputerDriverEnvironment):
|
|
|
78
74
|
|
|
79
75
|
@capture
|
|
80
76
|
async def grab(self) -> Image:
|
|
81
|
-
return await asyncio.to_thread(
|
|
77
|
+
return await asyncio.to_thread(mss_capture, mss.MSS)
|
|
82
78
|
|
|
83
79
|
@record
|
|
84
80
|
async def stream(self, hints: RecordHints) -> AsyncIterator[RecordedFrame]:
|
|
@@ -123,7 +119,7 @@ class X11Driver(ComputerDriverEnvironment):
|
|
|
123
119
|
@drag
|
|
124
120
|
async def drag_along(self, path, button, duration_ms, modifiers) -> None:
|
|
125
121
|
syms = [_keysym(k) for k in modifiers]
|
|
126
|
-
steps =
|
|
122
|
+
steps = drag_steps(path)
|
|
127
123
|
pause = duration_ms / 1000 / len(steps)
|
|
128
124
|
held: list[int] = []
|
|
129
125
|
|
|
@@ -217,7 +213,8 @@ class X11Driver(ComputerDriverEnvironment):
|
|
|
217
213
|
async def keys_up(self, keys) -> None:
|
|
218
214
|
def act(c):
|
|
219
215
|
for key in keys:
|
|
220
|
-
|
|
216
|
+
held = self._held.pop(key, None)
|
|
217
|
+
c.release(reversed(c.codes(_keysym(key))[-1:] if held is None else held))
|
|
221
218
|
await self._send(act)
|
|
222
219
|
|
|
223
220
|
async def _read(self, fn: Callable[[_Connection], Any]) -> Any:
|
|
@@ -368,24 +365,6 @@ class _Connection:
|
|
|
368
365
|
return {8 * i + bit for i, byte in enumerate(self.d.query_keymap()) for bit in range(8) if byte >> bit & 1}
|
|
369
366
|
|
|
370
367
|
|
|
371
|
-
def _grab() -> Image:
|
|
372
|
-
try:
|
|
373
|
-
with mss.MSS() as m:
|
|
374
|
-
shot = m.grab(m.monitors[0])
|
|
375
|
-
return Image(shot.width, shot.height, bytes(shot.bgra), "BGRA")
|
|
376
|
-
except Exception as e:
|
|
377
|
-
raise DeviceUnavailable(f"capture failed: {type(e).__name__}: {e}") from None
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def _steps(path: list) -> list[tuple[float, float]]:
|
|
381
|
-
"""The points a drag moves through after its start, about every DRAG_STEP_PX pixels."""
|
|
382
|
-
steps = []
|
|
383
|
-
for (x0, y0), (x1, y1) in zip(path, path[1:]):
|
|
384
|
-
n = max(2, int(math.hypot(x1 - x0, y1 - y0) / DRAG_STEP_PX))
|
|
385
|
-
steps += [(x0 + (x1 - x0) * k / n, y0 + (y1 - y0) * k / n) for k in range(1, n + 1)]
|
|
386
|
-
return steps
|
|
387
|
-
|
|
388
|
-
|
|
389
368
|
def _keysym(key: str) -> int:
|
|
390
369
|
if key in KEYSYMS:
|
|
391
370
|
return XK.string_to_keysym(KEYSYMS[key])
|