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