agentenv-framework-protocol 0.1.433__tar.gz → 0.1.434__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.433 → agentenv_framework_protocol-0.1.434}/PKG-INFO +23 -10
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/README.md +22 -9
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/app.py +7 -2
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/app_runtime.py +17 -2
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/website_browser.py +66 -8
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_website_browser.py +72 -11
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/client.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/app_data.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/apps.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/card.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/driver.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/errors.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/frames.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/operations.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/platform.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/results.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/runtime.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/session.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/computer/tools.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/probe_fake.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_client.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_app_server.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_driver.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_tool_context_injection.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.434
|
|
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
|
|
@@ -717,8 +717,9 @@ class MailApp(ComputerAppEnvironment):
|
|
|
717
717
|
|
|
718
718
|
- **`@computer_app`** names the platforms the app supports, as a driver names them, and the
|
|
719
719
|
`permissions` it needs of the computer. Its env.toml names the rest: the app itself
|
|
720
|
-
(`environment_name`), the `computer` and `driver` it deploys alone on,
|
|
721
|
-
build settings' values (`build_settings = { locale = "fr_FR" }`)
|
|
720
|
+
(`environment_name`), the `computer` and `driver` it deploys alone on, `build_settings`, its
|
|
721
|
+
build settings' values (`build_settings = { locale = "fr_FR" }`), and `[settings]`, defaults for its
|
|
722
|
+
`Settings` each deploy starts from.
|
|
722
723
|
- **`@linux(...)`** is how a Linux computer installs it, `Linux`'s fields: `files`, a folder of the app's
|
|
723
724
|
copied under its prefix, which holds the class's file; `packages`, the OS's, as apt takes them
|
|
724
725
|
(`Packages(apt=["name", "name=version", "name/suite"])`); `python`, a requirements file there,
|
|
@@ -754,9 +755,13 @@ class MailApp(ComputerAppEnvironment):
|
|
|
754
755
|
- **Its data** is under the `paths` its OS's decorator names, relative to the session user's home,
|
|
755
756
|
none inside another (`@linux(..., paths=["Mail", ".config/mail/accounts.json"])`), or what its own
|
|
756
757
|
`@add_data`, `@get_data` and `@reset_data` hooks serve instead (below).
|
|
757
|
-
- **`Settings`** is typed
|
|
758
|
-
in `
|
|
759
|
-
|
|
758
|
+
- **`Settings`** is typed. Its values come from the app env's `[settings]`, then the computer env's
|
|
759
|
+
`settings` beside the app in its `apps` (`apps = [{ env = "mail", settings = { signature = "-- Ada" } }]`),
|
|
760
|
+
then the deploy's `env_params` `app_settings`, each over the one before by top-level field (a table
|
|
761
|
+
or list is replaced whole). Each is checked at put; a required one may be left to the deploy, which
|
|
762
|
+
checks them merged. A computer hands each app its values as one JSON value in `APP_SETTINGS_ENV`
|
|
763
|
+
(`AGENTENV_APP_SETTINGS`, the apps' values by name), and the base class reads its own, by
|
|
764
|
+
`ENVIRONMENT_NAME`, as `self.settings`. A `SecretStr` field is a secret.
|
|
760
765
|
- **`BuildSettings`** is typed too: values the app's env fixes in its computer's image, which only
|
|
761
766
|
another image changes, so a deploy can't give one. The composer writes them to
|
|
762
767
|
`.agentenv/build_settings.json` in the app's folder, and the base class reads them, over the
|
|
@@ -808,9 +813,12 @@ are the profile's template. The base class adds:
|
|
|
808
813
|
- **Its settings**, `BrowserSettings`, which a subclass's `Settings` extends: `start_urls`, opened in
|
|
809
814
|
tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`), headless
|
|
810
815
|
the first from its command line and the rest over its pipe, since headless Chromium takes one; `window`,
|
|
811
|
-
1280 × 800, alone only; `locale`;
|
|
812
|
-
|
|
813
|
-
|
|
816
|
+
1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`; and `geolocation` and
|
|
817
|
+
`notifications`, `deny` (the default), which answers a page's prompt for it, or `ask`.
|
|
818
|
+
- **The flags agentenv owns**, after the subclass's command and before the start tabs: its profile, its
|
|
819
|
+
HTTP cache under `~/.cache` (`--disk-cache-dir`), `--remote-debugging-pipe`, `--no-first-run`,
|
|
820
|
+
`--no-default-browser-check`, `--lang` and, when `/dev/shm` holds under 1 GiB (Docker gives 64 MiB),
|
|
821
|
+
`--disable-dev-shm-usage`, so it keeps its shared memory in files. With
|
|
814
822
|
`AGENTENV_SITE_PROXY` set, it goes out through that proxy, with WebRTC's non-proxied UDP off.
|
|
815
823
|
Alone it bypasses nothing, loopback included; in a computer it bypasses loopback (`127.0.0.1`,
|
|
816
824
|
`[::1]`, `localhost`), so the apps beside it stay reachable while `*.localhost` names still go
|
|
@@ -818,6 +826,9 @@ are the profile's template. The base class adds:
|
|
|
818
826
|
except on the open web (`AGENTENV_BROWSER_OPEN_WEB=1`), and refuses to start with no proxy.
|
|
819
827
|
`declaration()` refuses a subclass whose command carries any of these, or `--remote-allow-origins`, or
|
|
820
828
|
any other way to open a debugging port.
|
|
829
|
+
- **Before each launch** it empties its cache, so no launch inherits another's, and writes the deploy's
|
|
830
|
+
prompt answers into its profile's `Default/Preferences` (`profile.default_content_setting_values`,
|
|
831
|
+
`2` to deny, left out to ask), which it reads as it starts.
|
|
821
832
|
- **Alone, no desktop**: its launch matches and waits on no window, its server adds no launcher and,
|
|
822
833
|
without `DISPLAY`, waits for no display, and its ready checks say when it's ready.
|
|
823
834
|
- **`write_policies`**, an install step that writes agentenv's unattended-use policies (`POLICIES`)
|
|
@@ -828,7 +839,9 @@ are the profile's template. The base class adds:
|
|
|
828
839
|
the result or raises `DevToolsError`, and `ConnectionError` with no browser connected. No port
|
|
829
840
|
listens and no `DevToolsActivePort` is written, so nothing else in the computer can reach it over
|
|
830
841
|
CDP. Since only the server that launched it holds the pipe, a server that restarts stops it and
|
|
831
|
-
launches it again
|
|
842
|
+
launches it again, and a launch while it runs, from its menu launcher say, opens another window over
|
|
843
|
+
the pipe rather than a second copy, which would hand its window to the first and exit. The ready check
|
|
844
|
+
`devtools_answers` asks `Browser.getVersion` over the pipe.
|
|
832
845
|
- **`browser/v1`** on its card, params only: `chromium` (its `version`), `clock: "real"` and, alone,
|
|
833
846
|
`window`.
|
|
834
847
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
@@ -661,8 +661,9 @@ class MailApp(ComputerAppEnvironment):
|
|
|
661
661
|
|
|
662
662
|
- **`@computer_app`** names the platforms the app supports, as a driver names them, and the
|
|
663
663
|
`permissions` it needs of the computer. Its env.toml names the rest: the app itself
|
|
664
|
-
(`environment_name`), the `computer` and `driver` it deploys alone on,
|
|
665
|
-
build settings' values (`build_settings = { locale = "fr_FR" }`)
|
|
664
|
+
(`environment_name`), the `computer` and `driver` it deploys alone on, `build_settings`, its
|
|
665
|
+
build settings' values (`build_settings = { locale = "fr_FR" }`), and `[settings]`, defaults for its
|
|
666
|
+
`Settings` each deploy starts from.
|
|
666
667
|
- **`@linux(...)`** is how a Linux computer installs it, `Linux`'s fields: `files`, a folder of the app's
|
|
667
668
|
copied under its prefix, which holds the class's file; `packages`, the OS's, as apt takes them
|
|
668
669
|
(`Packages(apt=["name", "name=version", "name/suite"])`); `python`, a requirements file there,
|
|
@@ -698,9 +699,13 @@ class MailApp(ComputerAppEnvironment):
|
|
|
698
699
|
- **Its data** is under the `paths` its OS's decorator names, relative to the session user's home,
|
|
699
700
|
none inside another (`@linux(..., paths=["Mail", ".config/mail/accounts.json"])`), or what its own
|
|
700
701
|
`@add_data`, `@get_data` and `@reset_data` hooks serve instead (below).
|
|
701
|
-
- **`Settings`** is typed
|
|
702
|
-
in `
|
|
703
|
-
|
|
702
|
+
- **`Settings`** is typed. Its values come from the app env's `[settings]`, then the computer env's
|
|
703
|
+
`settings` beside the app in its `apps` (`apps = [{ env = "mail", settings = { signature = "-- Ada" } }]`),
|
|
704
|
+
then the deploy's `env_params` `app_settings`, each over the one before by top-level field (a table
|
|
705
|
+
or list is replaced whole). Each is checked at put; a required one may be left to the deploy, which
|
|
706
|
+
checks them merged. A computer hands each app its values as one JSON value in `APP_SETTINGS_ENV`
|
|
707
|
+
(`AGENTENV_APP_SETTINGS`, the apps' values by name), and the base class reads its own, by
|
|
708
|
+
`ENVIRONMENT_NAME`, as `self.settings`. A `SecretStr` field is a secret.
|
|
704
709
|
- **`BuildSettings`** is typed too: values the app's env fixes in its computer's image, which only
|
|
705
710
|
another image changes, so a deploy can't give one. The composer writes them to
|
|
706
711
|
`.agentenv/build_settings.json` in the app's folder, and the base class reads them, over the
|
|
@@ -752,9 +757,12 @@ are the profile's template. The base class adds:
|
|
|
752
757
|
- **Its settings**, `BrowserSettings`, which a subclass's `Settings` extends: `start_urls`, opened in
|
|
753
758
|
tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`), headless
|
|
754
759
|
the first from its command line and the rest over its pipe, since headless Chromium takes one; `window`,
|
|
755
|
-
1280 × 800, alone only; `locale`;
|
|
756
|
-
|
|
757
|
-
|
|
760
|
+
1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`; and `geolocation` and
|
|
761
|
+
`notifications`, `deny` (the default), which answers a page's prompt for it, or `ask`.
|
|
762
|
+
- **The flags agentenv owns**, after the subclass's command and before the start tabs: its profile, its
|
|
763
|
+
HTTP cache under `~/.cache` (`--disk-cache-dir`), `--remote-debugging-pipe`, `--no-first-run`,
|
|
764
|
+
`--no-default-browser-check`, `--lang` and, when `/dev/shm` holds under 1 GiB (Docker gives 64 MiB),
|
|
765
|
+
`--disable-dev-shm-usage`, so it keeps its shared memory in files. With
|
|
758
766
|
`AGENTENV_SITE_PROXY` set, it goes out through that proxy, with WebRTC's non-proxied UDP off.
|
|
759
767
|
Alone it bypasses nothing, loopback included; in a computer it bypasses loopback (`127.0.0.1`,
|
|
760
768
|
`[::1]`, `localhost`), so the apps beside it stay reachable while `*.localhost` names still go
|
|
@@ -762,6 +770,9 @@ are the profile's template. The base class adds:
|
|
|
762
770
|
except on the open web (`AGENTENV_BROWSER_OPEN_WEB=1`), and refuses to start with no proxy.
|
|
763
771
|
`declaration()` refuses a subclass whose command carries any of these, or `--remote-allow-origins`, or
|
|
764
772
|
any other way to open a debugging port.
|
|
773
|
+
- **Before each launch** it empties its cache, so no launch inherits another's, and writes the deploy's
|
|
774
|
+
prompt answers into its profile's `Default/Preferences` (`profile.default_content_setting_values`,
|
|
775
|
+
`2` to deny, left out to ask), which it reads as it starts.
|
|
765
776
|
- **Alone, no desktop**: its launch matches and waits on no window, its server adds no launcher and,
|
|
766
777
|
without `DISPLAY`, waits for no display, and its ready checks say when it's ready.
|
|
767
778
|
- **`write_policies`**, an install step that writes agentenv's unattended-use policies (`POLICIES`)
|
|
@@ -772,7 +783,9 @@ are the profile's template. The base class adds:
|
|
|
772
783
|
the result or raises `DevToolsError`, and `ConnectionError` with no browser connected. No port
|
|
773
784
|
listens and no `DevToolsActivePort` is written, so nothing else in the computer can reach it over
|
|
774
785
|
CDP. Since only the server that launched it holds the pipe, a server that restarts stops it and
|
|
775
|
-
launches it again
|
|
786
|
+
launches it again, and a launch while it runs, from its menu launcher say, opens another window over
|
|
787
|
+
the pipe rather than a second copy, which would hand its window to the first and exit. The ready check
|
|
788
|
+
`devtools_answers` asks `Browser.getVersion` over the pipe.
|
|
776
789
|
- **`browser/v1`** on its card, params only: `chromium` (its `version`), `clock: "real"` and, alone,
|
|
777
790
|
`window`.
|
|
778
791
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
@@ -16,7 +16,7 @@ import json
|
|
|
16
16
|
import os
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
from pathlib import Path, PurePosixPath
|
|
19
|
-
from typing import Annotated, Any, Callable, ClassVar, Literal, Optional, Union
|
|
19
|
+
from typing import Annotated, Any, Awaitable, Callable, ClassVar, Literal, Optional, Union
|
|
20
20
|
|
|
21
21
|
from pydantic import (AfterValidator, BaseModel, ConfigDict, Field, TypeAdapter, ValidationError, create_model,
|
|
22
22
|
field_validator, model_validator)
|
|
@@ -409,7 +409,8 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
409
409
|
self.launch, {fn.__name__: getattr(self, fn.__name__) for fn in _methods(cls)
|
|
410
410
|
if hasattr(fn, _READY_CHECK_ATTR) and _runs_on(getattr(fn, _READY_CHECK_ATTR), on)},
|
|
411
411
|
ready.timeout_s if ready is not None else DEFAULT_TIMEOUT_S, desktop=self._on_desktop(),
|
|
412
|
-
launch_fds=self._launch_fds, relaunch_on_start=self._relaunch_on_start()
|
|
412
|
+
launch_fds=self._launch_fds, relaunch_on_start=self._relaunch_on_start(),
|
|
413
|
+
launch_running=self._launch_running())
|
|
413
414
|
self._computer_app_data = AppDataPlane(
|
|
414
415
|
self._computer_app, {op: getattr(self, name) for op, name in cls._data_hooks.items()}, self.paths,
|
|
415
416
|
cls.data_timeout_s)
|
|
@@ -431,6 +432,10 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
431
432
|
"""Whether only the server that launched it can talk to it, so a server that restarts launches it again."""
|
|
432
433
|
return False
|
|
433
434
|
|
|
435
|
+
def _launch_running(self) -> Optional[Callable[[], Awaitable[None]]]:
|
|
436
|
+
"""What a launch does while it runs instead of starting a second copy: nothing, so another copy starts."""
|
|
437
|
+
return None
|
|
438
|
+
|
|
434
439
|
@classmethod
|
|
435
440
|
def settings_on(cls, os_type: Optional[str]) -> type[BaseModel]:
|
|
436
441
|
"""Its ``Settings`` on ``os_type``, with the fields its OS adds."""
|
|
@@ -50,13 +50,15 @@ class AppRuntime:
|
|
|
50
50
|
|
|
51
51
|
def __init__(self, launch: Optional[Launch], ready_checks: dict[str, Callable[[], Any]], timeout_s: float, *,
|
|
52
52
|
desktop: bool = True, launch_fds: Optional[Callable[[], Awaitable[dict[int, int]]]] = None,
|
|
53
|
-
relaunch_on_start: bool = False
|
|
53
|
+
relaunch_on_start: bool = False,
|
|
54
|
+
launch_running: Optional[Callable[[], Awaitable[None]]] = None) -> None:
|
|
54
55
|
self.launch = launch
|
|
55
56
|
self.ready_checks = ready_checks
|
|
56
57
|
self.timeout_s = timeout_s
|
|
57
58
|
self.desktop = desktop
|
|
58
59
|
self.launch_fds = launch_fds
|
|
59
60
|
self.relaunch_on_start = relaunch_on_start
|
|
61
|
+
self.launch_running = launch_running
|
|
60
62
|
self.started = time.time()
|
|
61
63
|
self.launch_failed: Optional[str] = None
|
|
62
64
|
self.checking: dict[str, asyncio.Future] = {}
|
|
@@ -84,7 +86,11 @@ class AppRuntime:
|
|
|
84
86
|
|
|
85
87
|
async def launch_app(self) -> int:
|
|
86
88
|
"""Start the app from its folder, marked as launched, with the fds the app gives it, whose copies here are
|
|
87
|
-
closed once it has them; its pid.
|
|
89
|
+
closed once it has them; its pid. With ``launch_running``, an app this server launched that still runs does
|
|
90
|
+
that instead of starting a second copy, as a browser opens another window."""
|
|
91
|
+
if self.launch_running is not None and (running := _running()) is not None:
|
|
92
|
+
await self.launch_running()
|
|
93
|
+
return running
|
|
88
94
|
fds = await self.launch_fds() if self.launch_fds is not None else {}
|
|
89
95
|
try:
|
|
90
96
|
pid = await session.launch(self.launch.command, Path.cwd(),
|
|
@@ -156,6 +162,15 @@ def _launched_at() -> Optional[float]:
|
|
|
156
162
|
return None
|
|
157
163
|
|
|
158
164
|
|
|
165
|
+
def _running() -> Optional[int]:
|
|
166
|
+
"""The pid of the app this server launched last, while it runs."""
|
|
167
|
+
try:
|
|
168
|
+
pid = int(PID.read_text())
|
|
169
|
+
except (FileNotFoundError, ValueError):
|
|
170
|
+
return None
|
|
171
|
+
return pid if session.alive(pid) else None
|
|
172
|
+
|
|
173
|
+
|
|
159
174
|
async def _bounded(name: str, check: Awaitable[Optional[str]]) -> Optional[str]:
|
|
160
175
|
"""What ``check`` finds wrong, within ``CHECK_TIMEOUT_S``; a check that runs past it or raises says so."""
|
|
161
176
|
try:
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"""WebsiteBrowserEnvironment: a website browser as a computer app, run inside a computer or alone. A subclass says how
|
|
2
2
|
its build installs and starts; this class adds what agentenv owns, the same in both: its flags after the subclass's
|
|
3
3
|
command, the unattended-use policies, a CDP connection over the browser's debugging pipe, the settings every browser
|
|
4
|
-
takes,
|
|
5
|
-
|
|
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. Whether it runs alone, on the open web and through which site proxy come from
|
|
6
|
+
variables agentenv sets, never from its settings."""
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
9
|
import asyncio
|
|
9
10
|
import itertools
|
|
10
11
|
import json
|
|
12
|
+
import logging
|
|
11
13
|
import os
|
|
14
|
+
import shutil
|
|
12
15
|
from pathlib import Path
|
|
13
|
-
from typing import Any, ClassVar, Optional
|
|
16
|
+
from typing import Any, Awaitable, Callable, ClassVar, Literal, Optional
|
|
14
17
|
|
|
15
18
|
from pydantic import BaseModel, Field, field_validator
|
|
16
19
|
|
|
@@ -18,6 +21,8 @@ from .agent_env_environment import agent_skill
|
|
|
18
21
|
from .computer import AppDeclaration, ComputerAppEnvironment, Launch, install_step, ready_check
|
|
19
22
|
from .types import EnvironmentExtension
|
|
20
23
|
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
21
26
|
ALONE_ENV = "AGENTENV_BROWSER_ALONE"
|
|
22
27
|
OPEN_WEB_ENV = "AGENTENV_BROWSER_OPEN_WEB"
|
|
23
28
|
SITE_PROXY_ENV = "AGENTENV_SITE_PROXY"
|
|
@@ -25,7 +30,12 @@ BROWSER = "urn:agentenv:browser/v1"
|
|
|
25
30
|
#: The flags agentenv sets, by prefix, which a subclass's command may not carry: among them every way to open a debugging
|
|
26
31
|
#: port, since the browser is reached only over its pipe.
|
|
27
32
|
OWNED_FLAGS = ("--user-data-dir", "--remote-debugging-", "--remote-allow-origins", "--proxy-", "--no-proxy-server",
|
|
28
|
-
"--headless", "--window-size", "--lang", "--force-webrtc-ip-handling-policy"
|
|
33
|
+
"--headless", "--window-size", "--lang", "--force-webrtc-ip-handling-policy", "--disk-cache-dir",
|
|
34
|
+
"--disable-dev-shm-usage")
|
|
35
|
+
#: Below this much /dev/shm, as Docker's 64 MiB default, the browser keeps its shared memory in files instead.
|
|
36
|
+
SHM_MIN_BYTES = 1 << 30
|
|
37
|
+
#: Chromium's content setting that denies a permission without a prompt.
|
|
38
|
+
BLOCK = 2
|
|
29
39
|
#: Alone, loopback goes through the proxy too. In a computer, the apps on loopback beside it stay reachable; ``<-loopback>``
|
|
30
40
|
#: removes Chromium's own rule, so ``*.localhost`` names still don't.
|
|
31
41
|
ALONE_BYPASS = "<-loopback>"
|
|
@@ -147,6 +157,9 @@ class BrowserSettings(BaseModel):
|
|
|
147
157
|
locale: str = "en-US"
|
|
148
158
|
#: The IANA time zone its pages see.
|
|
149
159
|
time_zone: str = "UTC"
|
|
160
|
+
#: Whether a page asking for the location, or to show notifications, is denied without a prompt or asks.
|
|
161
|
+
geolocation: Literal["deny", "ask"] = "deny"
|
|
162
|
+
notifications: Literal["deny", "ask"] = "deny"
|
|
150
163
|
|
|
151
164
|
@field_validator("start_urls")
|
|
152
165
|
@classmethod
|
|
@@ -189,12 +202,20 @@ class WebsiteBrowserEnvironment(ComputerAppEnvironment):
|
|
|
189
202
|
def profile_dir(self) -> Path:
|
|
190
203
|
return Path.home() / self.profile
|
|
191
204
|
|
|
205
|
+
def cache_dir(self) -> Path:
|
|
206
|
+
"""Its HTTP cache, kept out of its profile, which holds only its data."""
|
|
207
|
+
return Path.home() / ".cache" / Path(self.profile).name
|
|
208
|
+
|
|
192
209
|
def agentenv_flags(self) -> list[str]:
|
|
193
210
|
"""The flags agentenv owns. A browser alone goes out only through its site proxy, so it won't launch without
|
|
194
211
|
one; in a computer it goes through one when the computer has one, loopback aside."""
|
|
195
212
|
settings, alone, proxy = self.settings, _alone(), os.environ.get(SITE_PROXY_ENV)
|
|
196
|
-
flags = [f"--user-data-dir={self.profile_dir()}", "--
|
|
197
|
-
"--no-default-browser-check", f"--lang={settings.locale}"]
|
|
213
|
+
flags = [f"--user-data-dir={self.profile_dir()}", f"--disk-cache-dir={self.cache_dir()}",
|
|
214
|
+
"--remote-debugging-pipe", "--no-first-run", "--no-default-browser-check", f"--lang={settings.locale}"]
|
|
215
|
+
if (shm := _shm_bytes()) < SHM_MIN_BYTES:
|
|
216
|
+
logger.info(f"/dev/shm holds {shm // 2**20} MiB, under {SHM_MIN_BYTES // 2**20} MiB: the browser keeps its "
|
|
217
|
+
"shared memory in files")
|
|
218
|
+
flags.append("--disable-dev-shm-usage")
|
|
198
219
|
if proxy:
|
|
199
220
|
flags += [f"--proxy-server={proxy}", f"--proxy-bypass-list={ALONE_BYPASS if alone else COMPUTER_BYPASS}",
|
|
200
221
|
"--force-webrtc-ip-handling-policy=disable_non_proxied_udp"]
|
|
@@ -228,8 +249,11 @@ class WebsiteBrowserEnvironment(ComputerAppEnvironment):
|
|
|
228
249
|
return self.settings.start_urls[:1] if _headless() else self.settings.start_urls
|
|
229
250
|
|
|
230
251
|
async def _launch_fds(self) -> dict[int, int]:
|
|
231
|
-
"""
|
|
232
|
-
connection, over which the start tabs its
|
|
252
|
+
"""Before each launch, an empty cache and the deploy's prompt answers in its profile; and a new debugging pipe:
|
|
253
|
+
the browser's ends as its fds 3 and 4, this server's ends its CDP connection, over which the start tabs its
|
|
254
|
+
command line can't open are opened."""
|
|
255
|
+
shutil.rmtree(self.cache_dir(), ignore_errors=True)
|
|
256
|
+
self._write_prompt_answers()
|
|
233
257
|
to_browser_read, to_browser_write = os.pipe()
|
|
234
258
|
from_browser_read, from_browser_write = os.pipe()
|
|
235
259
|
await self.devtools.connect(to_browser_write, from_browser_read)
|
|
@@ -239,6 +263,24 @@ class WebsiteBrowserEnvironment(ComputerAppEnvironment):
|
|
|
239
263
|
self._start_tabs.add_done_callback(lambda done: done.cancelled() or done.exception())
|
|
240
264
|
return {PIPE_IN: to_browser_read, PIPE_OUT: from_browser_write}
|
|
241
265
|
|
|
266
|
+
def _write_prompt_answers(self) -> None:
|
|
267
|
+
"""Its profile's preferences, which it reads as it starts, denying location and notification prompts or
|
|
268
|
+
leaving them to ask, as the deploy's settings say."""
|
|
269
|
+
preferences = self.profile_dir() / "Default" / "Preferences"
|
|
270
|
+
try:
|
|
271
|
+
current = json.loads(preferences.read_text(encoding="utf-8"))
|
|
272
|
+
except (OSError, ValueError):
|
|
273
|
+
current = {}
|
|
274
|
+
current = current if isinstance(current, dict) else {}
|
|
275
|
+
defaults = current.setdefault("profile", {}).setdefault("default_content_setting_values", {})
|
|
276
|
+
for name in ("geolocation", "notifications"):
|
|
277
|
+
if getattr(self.settings, name) == "deny":
|
|
278
|
+
defaults[name] = BLOCK
|
|
279
|
+
else:
|
|
280
|
+
defaults.pop(name, None)
|
|
281
|
+
preferences.parent.mkdir(parents=True, exist_ok=True)
|
|
282
|
+
preferences.write_text(json.dumps(current), encoding="utf-8")
|
|
283
|
+
|
|
242
284
|
async def _open_tabs(self, urls: list[str]) -> None:
|
|
243
285
|
"""Each of ``urls`` in a tab behind the active one, in order."""
|
|
244
286
|
for url in urls:
|
|
@@ -247,6 +289,14 @@ class WebsiteBrowserEnvironment(ComputerAppEnvironment):
|
|
|
247
289
|
def _relaunch_on_start(self) -> bool:
|
|
248
290
|
return True
|
|
249
291
|
|
|
292
|
+
def _launch_running(self) -> Callable[[], Awaitable[None]]:
|
|
293
|
+
return self._open_window
|
|
294
|
+
|
|
295
|
+
async def _open_window(self) -> None:
|
|
296
|
+
"""Another window, which a launch of the browser while it runs opens over its pipe: a second copy would only
|
|
297
|
+
hand its window to the first and exit, taking its new pipe with it."""
|
|
298
|
+
await self.devtools.call("Target.createTarget", {"url": "about:blank", "newWindow": True})
|
|
299
|
+
|
|
250
300
|
@install_step(scope="system")
|
|
251
301
|
def write_policies(self) -> None:
|
|
252
302
|
"""agentenv's unattended-use policies, with the subclass's over them, where its build reads them."""
|
|
@@ -286,6 +336,14 @@ def _open_web() -> bool:
|
|
|
286
336
|
return os.environ.get(OPEN_WEB_ENV) == "1"
|
|
287
337
|
|
|
288
338
|
|
|
339
|
+
def _shm_bytes(path: str = "/dev/shm") -> int:
|
|
340
|
+
try:
|
|
341
|
+
stats = os.statvfs(path)
|
|
342
|
+
except OSError:
|
|
343
|
+
return 0
|
|
344
|
+
return stats.f_frsize * stats.f_blocks
|
|
345
|
+
|
|
346
|
+
|
|
289
347
|
def _headless() -> bool:
|
|
290
348
|
"""Alone it runs headless, but headful on Xvfb for the open web, whose sites block headless browsers."""
|
|
291
349
|
return _alone() and not _open_web()
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
"""WebsiteBrowserEnvironment: agentenv's flags after a subclass's command, in a computer and alone, and the proxy rule
|
|
2
2
|
for each; a subclass refused for carrying one of them; the start tabs, the time zone and the window from its settings;
|
|
3
3
|
its launch run with no desktop when alone; a CDP connection over a new debugging pipe at each launch, which its ready
|
|
4
|
-
check asks, and a browser launched again when its server restarts; its
|
|
5
|
-
|
|
4
|
+
check asks, and a browser launched again when its server restarts; its shared memory in files only on a small
|
|
5
|
+
/dev/shm; an empty cache and the deploy's prompt answers in its profile at each launch; its policies, card params and
|
|
6
|
+
skill; and an app that isn't a browser launched and declared as before."""
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
9
|
import asyncio
|
|
9
10
|
import json
|
|
11
|
+
import os
|
|
10
12
|
import sys
|
|
11
13
|
|
|
12
14
|
import pytest
|
|
13
15
|
|
|
14
16
|
from agentenv_protocol.computer import APP_SETTINGS_ENV, ComputerAppEnvironment, Launch, Ready, app, linux, session
|
|
17
|
+
from agentenv_protocol import website_browser
|
|
15
18
|
from agentenv_protocol.computer import app_runtime
|
|
16
19
|
from agentenv_protocol.computer.platform import Platform
|
|
17
20
|
from agentenv_protocol.website_browser import (ALONE_BYPASS, ALONE_ENV, BROWSER, COMPUTER_BYPASS, OPEN_WEB_ENV,
|
|
@@ -19,9 +22,11 @@ from agentenv_protocol.website_browser import (ALONE_BYPASS, ALONE_ENV, BROWSER,
|
|
|
19
22
|
WebsiteBrowserEnvironment)
|
|
20
23
|
|
|
21
24
|
PROXY = "http://127.0.0.1:3128"
|
|
22
|
-
OWNED = ["--user-data-dir", "--
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
+
OWNED = ["--user-data-dir", "--disk-cache-dir", "--remote-debugging-pipe", "--no-first-run",
|
|
26
|
+
"--no-default-browser-check", "--lang=en-US"]
|
|
27
|
+
# A browser that answers CDP on its debugging pipe, fds 3 and 4, as a Chromium does: Browser.getVersion with its
|
|
28
|
+
# product, Target.createTarget by writing the tab's URL to tabs.txt beside it, a new window's after "window ", anything
|
|
29
|
+
# else with an error.
|
|
25
30
|
FAKE_BROWSER = """import json, os
|
|
26
31
|
buffer = b""
|
|
27
32
|
while chunk := os.read(3, 65536):
|
|
@@ -31,9 +36,9 @@ while chunk := os.read(3, 65536):
|
|
|
31
36
|
message = json.loads(raw)
|
|
32
37
|
if message["method"] == "Browser.getVersion":
|
|
33
38
|
answer = {"result": {"product": "Fake/1"}}
|
|
34
|
-
elif message["method"] == "Target.createTarget"
|
|
39
|
+
elif message["method"] == "Target.createTarget":
|
|
35
40
|
with open("tabs.txt", "a") as tabs:
|
|
36
|
-
tabs.write(message["params"]["url"] + "\\n")
|
|
41
|
+
tabs.write(("window " if message["params"].get("newWindow") else "") + message["params"]["url"] + "\\n")
|
|
37
42
|
answer = {"result": {"targetId": message["params"]["url"]}}
|
|
38
43
|
else:
|
|
39
44
|
answer = {"error": {"code": -32601, "message": "unknown method"}}
|
|
@@ -60,8 +65,10 @@ class OnDemandBrowser(WebsiteBrowserEnvironment):
|
|
|
60
65
|
|
|
61
66
|
@pytest.fixture(autouse=True)
|
|
62
67
|
def browser_home(monkeypatch, tmp_path):
|
|
63
|
-
"""A browser on Linux, its session user's home under ``tmp_path``, inside a computer with no site proxy
|
|
68
|
+
"""A browser on Linux, its session user's home under ``tmp_path``, inside a computer with no site proxy and 1 GiB
|
|
69
|
+
of /dev/shm."""
|
|
64
70
|
monkeypatch.setattr(app, "detect", lambda: Platform(os="linux"))
|
|
71
|
+
monkeypatch.setattr(website_browser, "_shm_bytes", lambda path="/dev/shm": 1 << 30)
|
|
65
72
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
66
73
|
(tmp_path / "home").mkdir()
|
|
67
74
|
monkeypatch.setenv("ENVIRONMENT_NAME", "chromium")
|
|
@@ -78,7 +85,8 @@ def test_in_a_computer_its_flags_follow_its_command_then_its_start_tabs_and_its_
|
|
|
78
85
|
launch = Browser().launch
|
|
79
86
|
|
|
80
87
|
profile = f"--user-data-dir={browser_home / '.config/chromium'}"
|
|
81
|
-
|
|
88
|
+
cache = f"--disk-cache-dir={browser_home / '.cache/chromium'}"
|
|
89
|
+
assert launch.command == ["chromium/chrome", "--no-sandbox", profile, cache, *OWNED[2:], "about:blank"]
|
|
82
90
|
assert launch.env == {"TZ": "UTC"}
|
|
83
91
|
assert launch.window == {"class": "Chromium"} and launch.ready == Ready(window=True, timeout_s=30)
|
|
84
92
|
|
|
@@ -89,7 +97,7 @@ def test_in_a_computer_with_a_site_proxy_only_loopback_bypasses_it(monkeypatch):
|
|
|
89
97
|
command = Browser().launch.command
|
|
90
98
|
|
|
91
99
|
assert [f"--proxy-server={PROXY}", f"--proxy-bypass-list={COMPUTER_BYPASS}",
|
|
92
|
-
"--force-webrtc-ip-handling-policy=disable_non_proxied_udp"] == command[
|
|
100
|
+
"--force-webrtc-ip-handling-policy=disable_non_proxied_udp"] == command[8:11]
|
|
93
101
|
assert not any(flag.startswith(("--headless", "--window-size")) for flag in command)
|
|
94
102
|
|
|
95
103
|
|
|
@@ -142,7 +150,8 @@ def test_alone_it_refuses_to_launch_without_a_site_proxy(monkeypatch):
|
|
|
142
150
|
|
|
143
151
|
|
|
144
152
|
@pytest.mark.parametrize("flag", ["--remote-debugging-port=9222", "--remote-allow-origins=*", "--headless",
|
|
145
|
-
"--proxy-server=http://elsewhere:3128", "--user-data-dir=/tmp/profile"
|
|
153
|
+
"--proxy-server=http://elsewhere:3128", "--user-data-dir=/tmp/profile",
|
|
154
|
+
"--disk-cache-dir=/tmp/cache", "--disable-dev-shm-usage"])
|
|
146
155
|
def test_a_subclass_whose_command_carries_a_flag_agentenv_owns_is_refused_when_declared(flag):
|
|
147
156
|
@linux(launch=Launch(command=["chromium/chrome", flag]))
|
|
148
157
|
class Widened(WebsiteBrowserEnvironment):
|
|
@@ -152,6 +161,37 @@ def test_a_subclass_whose_command_carries_a_flag_agentenv_owns_is_refused_when_d
|
|
|
152
161
|
Widened.declaration()
|
|
153
162
|
|
|
154
163
|
|
|
164
|
+
@pytest.mark.parametrize("mib, in_files", [(64, True), (1024, False)], ids=["docker-default", "sized"])
|
|
165
|
+
def test_it_keeps_its_shared_memory_in_files_only_when_dev_shm_is_under_1_gib(monkeypatch, mib, in_files):
|
|
166
|
+
monkeypatch.setattr(website_browser, "_shm_bytes", lambda path="/dev/shm": mib * 2**20)
|
|
167
|
+
|
|
168
|
+
assert ("--disable-dev-shm-usage" in Browser().launch.command) is in_files
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@pytest.mark.asyncio
|
|
172
|
+
@pytest.mark.parametrize("settings, prompts", [
|
|
173
|
+
({}, {"geolocation": 2, "notifications": 2}),
|
|
174
|
+
({"notifications": "ask"}, {"geolocation": 2}),
|
|
175
|
+
], ids=["deny", "ask"])
|
|
176
|
+
async def test_each_launch_starts_with_an_empty_cache_and_the_deploys_prompt_answers_in_its_profile(
|
|
177
|
+
browser_home, monkeypatch, settings, prompts):
|
|
178
|
+
_settings(monkeypatch, **settings)
|
|
179
|
+
preferences = browser_home / ".config/chromium/Default/Preferences"
|
|
180
|
+
preferences.parent.mkdir(parents=True)
|
|
181
|
+
preferences.write_text(json.dumps({"homepage": "about:blank", "profile": {"default_content_setting_values": {
|
|
182
|
+
"notifications": 1, "cookies": 1}}}))
|
|
183
|
+
(browser_home / ".cache/chromium/Cache").mkdir(parents=True)
|
|
184
|
+
browser = Browser()
|
|
185
|
+
|
|
186
|
+
for fd in (await browser._launch_fds()).values():
|
|
187
|
+
os.close(fd)
|
|
188
|
+
browser.devtools.close()
|
|
189
|
+
|
|
190
|
+
assert json.loads(preferences.read_text()) == {"homepage": "about:blank", "profile": {
|
|
191
|
+
"default_content_setting_values": {"cookies": 1, **prompts}}}
|
|
192
|
+
assert not (browser_home / ".cache/chromium").exists()
|
|
193
|
+
|
|
194
|
+
|
|
155
195
|
def test_its_settings_give_its_start_tabs_language_and_time_zone_and_no_start_url_is_a_flag(monkeypatch):
|
|
156
196
|
_settings(monkeypatch, start_urls=["https://slack.com", "https://linear.app"], locale="fr-FR",
|
|
157
197
|
time_zone="Europe/Paris")
|
|
@@ -242,6 +282,26 @@ async def test_each_launch_gets_a_new_pipe_its_server_talks_cdp_over_and_its_rea
|
|
|
242
282
|
assert "devtools_answers" in runtime.ready_checks
|
|
243
283
|
|
|
244
284
|
|
|
285
|
+
@pytest.mark.asyncio
|
|
286
|
+
async def test_a_launch_while_it_runs_opens_a_window_over_its_pipe_and_starts_no_second_copy(monkeypatch, tmp_path,
|
|
287
|
+
browser_home):
|
|
288
|
+
monkeypatch.chdir(tmp_path)
|
|
289
|
+
monkeypatch.setattr(app_runtime, "LAUNCHED", tmp_path / "launched")
|
|
290
|
+
browser = FakeBrowser()
|
|
291
|
+
runtime = browser._computer_app
|
|
292
|
+
|
|
293
|
+
first = await runtime.launch_app()
|
|
294
|
+
(browser_home / ".cache/chromium").mkdir(parents=True, exist_ok=True)
|
|
295
|
+
(browser_home / ".cache/chromium/kept").touch()
|
|
296
|
+
second = await runtime.launch_app()
|
|
297
|
+
answers = await browser.devtools_answers()
|
|
298
|
+
await runtime.stop_app()
|
|
299
|
+
browser.devtools.close()
|
|
300
|
+
|
|
301
|
+
assert second == first and answers is True and (browser_home / ".cache/chromium/kept").exists()
|
|
302
|
+
assert (tmp_path / "tabs.txt").read_text().splitlines() == ["window about:blank"]
|
|
303
|
+
|
|
304
|
+
|
|
245
305
|
@pytest.mark.asyncio
|
|
246
306
|
async def test_headless_it_opens_one_start_tab_from_its_command_line_and_the_rest_over_its_pipe(monkeypatch, tmp_path):
|
|
247
307
|
monkeypatch.chdir(tmp_path)
|
|
@@ -356,6 +416,7 @@ def test_an_app_that_isnt_a_browser_launches_and_declares_as_before():
|
|
|
356
416
|
stored = Mail.declaration().model_dump(mode="json", exclude_none=True)
|
|
357
417
|
|
|
358
418
|
assert mail.launch is Mail._installs["linux"].launch and mail._computer_app.desktop is True
|
|
419
|
+
assert mail._computer_app.launch_running is None
|
|
359
420
|
assert stored["os"]["linux"]["launch"] == {"at": "boot", "command": ["mail"], "window": {"class": "Mail"},
|
|
360
421
|
"ready": {"window": True, "timeout_s": 60.0}}
|
|
361
422
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/THIRD_PARTY_NOTICES.md
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/examples/basic_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/computer_fake.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/probe_fake.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_a2a_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_errors.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_intake.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_manifest.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.433 → agentenv_framework_protocol-0.1.434}/tests/test_preflight.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|