agentenv-framework-protocol 0.1.434__tar.gz → 0.1.437__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.434 → agentenv_framework_protocol-0.1.437}/PKG-INFO +19 -5
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/README.md +18 -4
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/app_data.py +4 -2
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/app_runtime.py +57 -9
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/dataplane.py +19 -10
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/session.py +28 -1
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/website_browser.py +265 -27
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_dataplane.py +43 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_website_browser.py +175 -8
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/client.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/app.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/apps.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/card.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/driver.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/errors.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/frames.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/operations.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/platform.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/results.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/runtime.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/computer/tools.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/probe_fake.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_client.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_app_server.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_driver.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.434 → agentenv_framework_protocol-0.1.437}/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.437
|
|
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
|
|
@@ -813,8 +813,11 @@ are the profile's template. The base class adds:
|
|
|
813
813
|
- **Its settings**, `BrowserSettings`, which a subclass's `Settings` extends: `start_urls`, opened in
|
|
814
814
|
tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`), headless
|
|
815
815
|
the first from its command line and the rest over its pipe, since headless Chromium takes one; `window`,
|
|
816
|
-
1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`;
|
|
817
|
-
`notifications`, `deny` (the default), which answers a page's prompt for it, or `ask
|
|
816
|
+
1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`; `geolocation` and
|
|
817
|
+
`notifications`, `deny` (the default), which answers a page's prompt for it, or `ask`; and `dialogs`, who
|
|
818
|
+
answers a page's JavaScript dialogs: `agent` (the default), or agentenv, accepting (`accept`, a `prompt()`
|
|
819
|
+
with its default text) or dismissing (`dismiss`) each. Either way agentenv leaves a page that asks before
|
|
820
|
+
it's left (`beforeunload`), so none of its answers blocks a navigation.
|
|
818
821
|
- **The flags agentenv owns**, after the subclass's command and before the start tabs: its profile, its
|
|
819
822
|
HTTP cache under `~/.cache` (`--disk-cache-dir`), `--remote-debugging-pipe`, `--no-first-run`,
|
|
820
823
|
`--no-default-browser-check`, `--lang` and, when `/dev/shm` holds under 1 GiB (Docker gives 64 MiB),
|
|
@@ -842,8 +845,19 @@ are the profile's template. The base class adds:
|
|
|
842
845
|
launches it again, and a launch while it runs, from its menu launcher say, opens another window over
|
|
843
846
|
the pipe rather than a second copy, which would hand its window to the first and exit. The ready check
|
|
844
847
|
`devtools_answers` asks `Browser.getVersion` over the pipe.
|
|
845
|
-
-
|
|
846
|
-
`
|
|
848
|
+
- **Its control connection**, `self.control`, a `BrowserControl` over the pipe. At each launch it attaches
|
|
849
|
+
to every page with flattened sessions and keeps a record, `since` its server started: open tabs in
|
|
850
|
+
opened order, the one whose page has focus `active`; `closed_tabs`, each with `closed_at` and
|
|
851
|
+
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
852
|
+
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
853
|
+
and, completed, its size and sha256; and `dialogs`, each with its type, message, answer and who gave it.
|
|
854
|
+
`hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
|
|
855
|
+
- **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
|
|
856
|
+
`record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
|
|
857
|
+
launch (with its cause: `boot`, `server_restart`, `launch`, `data/add` or `data/reset`), each stop it made
|
|
858
|
+
and each exit it didn't, with its exit `code` or the `signal` that ended it, since the computer booted.
|
|
859
|
+
Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
|
|
860
|
+
failure, its ready check false.
|
|
847
861
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
848
862
|
`~/Downloads`, folders it makes.
|
|
849
863
|
|
|
@@ -757,8 +757,11 @@ are the profile's template. The base class adds:
|
|
|
757
757
|
- **Its settings**, `BrowserSettings`, which a subclass's `Settings` extends: `start_urls`, opened in
|
|
758
758
|
tabs at launch, the first active (`["about:blank"]` by default; none may start with `-`), headless
|
|
759
759
|
the first from its command line and the rest over its pipe, since headless Chromium takes one; `window`,
|
|
760
|
-
1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`;
|
|
761
|
-
`notifications`, `deny` (the default), which answers a page's prompt for it, or `ask
|
|
760
|
+
1280 × 800, alone only; `locale`; `time_zone`, set as `TZ` in its launch's `env`; `geolocation` and
|
|
761
|
+
`notifications`, `deny` (the default), which answers a page's prompt for it, or `ask`; and `dialogs`, who
|
|
762
|
+
answers a page's JavaScript dialogs: `agent` (the default), or agentenv, accepting (`accept`, a `prompt()`
|
|
763
|
+
with its default text) or dismissing (`dismiss`) each. Either way agentenv leaves a page that asks before
|
|
764
|
+
it's left (`beforeunload`), so none of its answers blocks a navigation.
|
|
762
765
|
- **The flags agentenv owns**, after the subclass's command and before the start tabs: its profile, its
|
|
763
766
|
HTTP cache under `~/.cache` (`--disk-cache-dir`), `--remote-debugging-pipe`, `--no-first-run`,
|
|
764
767
|
`--no-default-browser-check`, `--lang` and, when `/dev/shm` holds under 1 GiB (Docker gives 64 MiB),
|
|
@@ -786,8 +789,19 @@ are the profile's template. The base class adds:
|
|
|
786
789
|
launches it again, and a launch while it runs, from its menu launcher say, opens another window over
|
|
787
790
|
the pipe rather than a second copy, which would hand its window to the first and exit. The ready check
|
|
788
791
|
`devtools_answers` asks `Browser.getVersion` over the pipe.
|
|
789
|
-
-
|
|
790
|
-
`
|
|
792
|
+
- **Its control connection**, `self.control`, a `BrowserControl` over the pipe. At each launch it attaches
|
|
793
|
+
to every page with flattened sessions and keeps a record, `since` its server started: open tabs in
|
|
794
|
+
opened order, the one whose page has focus `active`; `closed_tabs`, each with `closed_at` and
|
|
795
|
+
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
796
|
+
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
797
|
+
and, completed, its size and sha256; and `dialogs`, each with its type, message, answer and who gave it.
|
|
798
|
+
`hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
|
|
799
|
+
- **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
|
|
800
|
+
`record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
|
|
801
|
+
launch (with its cause: `boot`, `server_restart`, `launch`, `data/add` or `data/reset`), each stop it made
|
|
802
|
+
and each exit it didn't, with its exit `code` or the `signal` that ended it, since the computer booted.
|
|
803
|
+
Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
|
|
804
|
+
failure, its ready check false.
|
|
791
805
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
792
806
|
`~/Downloads`, folders it makes.
|
|
793
807
|
|
|
@@ -15,7 +15,7 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
15
15
|
|
|
16
16
|
from ..agent_env_environment import OP_ADD_DATA, OP_GET_DATA, OP_RESET_DATA
|
|
17
17
|
from ..transfers import WriteNamespaceGrant
|
|
18
|
-
from ..types import FilePart, uploaded_object_path
|
|
18
|
+
from ..types import METHOD_ADD, METHOD_RESET, FilePart, uploaded_object_path
|
|
19
19
|
from .dataplane import file_parts, part_bytes
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -23,6 +23,8 @@ if TYPE_CHECKING:
|
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
|
+
#: The cause a launch after each data call logs.
|
|
27
|
+
_CAUSES = {OP_ADD_DATA: METHOD_ADD, OP_RESET_DATA: METHOD_RESET}
|
|
26
28
|
#: Where an app's server keeps its paths as installed, in the app's folder.
|
|
27
29
|
INITIAL = Path(".agentenv/initial")
|
|
28
30
|
|
|
@@ -80,7 +82,7 @@ class AppDataPlane:
|
|
|
80
82
|
raise
|
|
81
83
|
finally:
|
|
82
84
|
if running:
|
|
83
|
-
await self.runtime.launch_app()
|
|
85
|
+
await self.runtime.launch_app(_CAUSES[op])
|
|
84
86
|
except Exception as e:
|
|
85
87
|
if not answer.done():
|
|
86
88
|
answer.set_exception(e)
|
|
@@ -5,10 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import inspect
|
|
8
|
+
import json
|
|
8
9
|
import os
|
|
9
10
|
import shlex
|
|
11
|
+
import signal
|
|
10
12
|
import time
|
|
11
13
|
from collections.abc import Awaitable, Callable
|
|
14
|
+
from datetime import datetime, timezone
|
|
12
15
|
from pathlib import Path
|
|
13
16
|
from typing import TYPE_CHECKING, Any, Optional
|
|
14
17
|
|
|
@@ -26,6 +29,11 @@ CHECK_TIMEOUT_S = 10.0
|
|
|
26
29
|
#: Where an app's server keeps the pid of the app it launched last, in the app's folder, so a server that restarts can
|
|
27
30
|
#: still stop it.
|
|
28
31
|
PID = Path(".agentenv/app.pid")
|
|
32
|
+
#: Where an app's server logs each launch, stop and exit of the app, beside its pid; a boot starts the app's folder as
|
|
33
|
+
#: its image has it, so the log is the boot's.
|
|
34
|
+
LAUNCHES = Path(".agentenv/launches")
|
|
35
|
+
#: How long an app that ended on its own may take to be reaped, for its exit code.
|
|
36
|
+
EXIT_WAIT_S = 5.0
|
|
29
37
|
#: How long a stopped app may take to exit before it's killed.
|
|
30
38
|
STOP_TIMEOUT_S = 10.0
|
|
31
39
|
|
|
@@ -59,6 +67,7 @@ class AppRuntime:
|
|
|
59
67
|
self.launch_fds = launch_fds
|
|
60
68
|
self.relaunch_on_start = relaunch_on_start
|
|
61
69
|
self.launch_running = launch_running
|
|
70
|
+
self.exits: set[asyncio.Future] = set()
|
|
62
71
|
self.started = time.time()
|
|
63
72
|
self.launch_failed: Optional[str] = None
|
|
64
73
|
self.checking: dict[str, asyncio.Future] = {}
|
|
@@ -80,14 +89,14 @@ class AppRuntime:
|
|
|
80
89
|
try:
|
|
81
90
|
if relaunch:
|
|
82
91
|
await self.stop_app()
|
|
83
|
-
await self.launch_app()
|
|
92
|
+
await self.launch_app("server_restart" if relaunch else "boot")
|
|
84
93
|
except Exception as e:
|
|
85
94
|
self.launch_failed = f"{type(e).__name__}: {e}"
|
|
86
95
|
|
|
87
|
-
async def launch_app(self) -> int:
|
|
96
|
+
async def launch_app(self, cause: str = "launch") -> int:
|
|
88
97
|
"""Start the app from its folder, marked as launched, with the fds the app gives it, whose copies here are
|
|
89
|
-
closed once it has them
|
|
90
|
-
that instead of starting a second copy, as a browser opens another window."""
|
|
98
|
+
closed once it has them, and logged with ``cause``; its pid. With ``launch_running``, an app this server
|
|
99
|
+
launched that still runs does that instead of starting a second copy, as a browser opens another window."""
|
|
91
100
|
if self.launch_running is not None and (running := _running()) is not None:
|
|
92
101
|
await self.launch_running()
|
|
93
102
|
return running
|
|
@@ -104,6 +113,7 @@ class AppRuntime:
|
|
|
104
113
|
PID.write_text(str(pid))
|
|
105
114
|
_mark().parent.mkdir(parents=True, exist_ok=True)
|
|
106
115
|
_mark().touch()
|
|
116
|
+
self.log("launch", pid, cause=cause)
|
|
107
117
|
return pid
|
|
108
118
|
|
|
109
119
|
async def stop_app(self) -> bool:
|
|
@@ -115,9 +125,39 @@ class AppRuntime:
|
|
|
115
125
|
PID.unlink()
|
|
116
126
|
if not session.alive(pid):
|
|
117
127
|
return False
|
|
128
|
+
self.log("stop", pid)
|
|
118
129
|
await session.stop(pid, STOP_TIMEOUT_S)
|
|
119
130
|
return True
|
|
120
131
|
|
|
132
|
+
def exited(self) -> bool:
|
|
133
|
+
"""Log that the app this server launched last ended on its own, with its exit code or the signal that ended
|
|
134
|
+
it, unless the server stopped it; whether it did."""
|
|
135
|
+
if (pid := _recorded_pid()) is None:
|
|
136
|
+
return False
|
|
137
|
+
noting = asyncio.ensure_future(self._log_exit(pid))
|
|
138
|
+
self.exits.add(noting)
|
|
139
|
+
noting.add_done_callback(self.exits.discard)
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
async def _log_exit(self, pid: int) -> None:
|
|
143
|
+
code = await session.returncode(pid, EXIT_WAIT_S)
|
|
144
|
+
self.log("exit", pid, **({} if code is None else
|
|
145
|
+
{"signal": signal.Signals(-code).name} if code < 0 else {"code": code}))
|
|
146
|
+
|
|
147
|
+
def log(self, event: str, pid: int, **fields: Any) -> None:
|
|
148
|
+
"""Append ``event`` for the app's ``pid`` to its launch log, which outlives its server for the boot."""
|
|
149
|
+
entry = {"at": datetime.now(timezone.utc).isoformat(), "event": event, "pid": pid, **fields}
|
|
150
|
+
_log().parent.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
with _log().open("a", encoding="utf-8") as log:
|
|
152
|
+
log.write(json.dumps(entry) + "\n")
|
|
153
|
+
|
|
154
|
+
def launches(self) -> list[dict]:
|
|
155
|
+
"""Every launch, stop and exit of the app since the computer booted, in order."""
|
|
156
|
+
try:
|
|
157
|
+
return [json.loads(line) for line in _log().read_text(encoding="utf-8").splitlines() if line]
|
|
158
|
+
except FileNotFoundError:
|
|
159
|
+
return []
|
|
160
|
+
|
|
121
161
|
async def readiness(self) -> AppReadiness:
|
|
122
162
|
ready = self.launch.ready if self.launch is not None else None
|
|
123
163
|
checks: list[tuple[str, Awaitable[Optional[str]]]] = []
|
|
@@ -154,6 +194,17 @@ def _mark() -> Path:
|
|
|
154
194
|
return LAUNCHED / Path.cwd().name
|
|
155
195
|
|
|
156
196
|
|
|
197
|
+
def _log() -> Path:
|
|
198
|
+
return LAUNCHES
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _recorded_pid() -> Optional[int]:
|
|
202
|
+
try:
|
|
203
|
+
return int(PID.read_text())
|
|
204
|
+
except (FileNotFoundError, ValueError):
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
|
|
157
208
|
def _launched_at() -> Optional[float]:
|
|
158
209
|
"""When the app was last launched since the computer booted, by its mark."""
|
|
159
210
|
try:
|
|
@@ -164,11 +215,8 @@ def _launched_at() -> Optional[float]:
|
|
|
164
215
|
|
|
165
216
|
def _running() -> Optional[int]:
|
|
166
217
|
"""The pid of the app this server launched last, while it runs."""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
except (FileNotFoundError, ValueError):
|
|
170
|
-
return None
|
|
171
|
-
return pid if session.alive(pid) else None
|
|
218
|
+
pid = _recorded_pid()
|
|
219
|
+
return pid if pid is not None and session.alive(pid) else None
|
|
172
220
|
|
|
173
221
|
|
|
174
222
|
async def _bounded(name: str, check: Awaitable[Optional[str]]) -> Optional[str]:
|
|
@@ -15,7 +15,7 @@ import httpx
|
|
|
15
15
|
from pydantic import TypeAdapter, ValidationError
|
|
16
16
|
|
|
17
17
|
from ..agent_env_environment import add_data, get_data
|
|
18
|
-
from ..transfers import NamespaceUploader, ReadObject, RelativePath, WriteNamespaceGrant, download
|
|
18
|
+
from ..transfers import NamespaceUploader, ReadObject, RelativePath, TransferError, WriteNamespaceGrant, download
|
|
19
19
|
from ..types import DATA_OBJECTS_EXTENSION_URI, FilePart, FileWithBytes, FileWithUri, uploaded_file_part, uploaded_object_path
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -64,22 +64,31 @@ class DataPlane:
|
|
|
64
64
|
async def file_parts(home: Path, paths: list[str], write_namespace: Optional[WriteNamespaceGrant],
|
|
65
65
|
leaving_out: list[str] = ()) -> list:
|
|
66
66
|
"""A part for each file under ``paths`` of ``home`` but under none of ``leaving_out``, naming its path relative to
|
|
67
|
-
``home``: uploaded under ``write_namespace`` when there is one, else inline.
|
|
67
|
+
``home``: uploaded under ``write_namespace`` when there is one, else inline. A file a running app removes before it's
|
|
68
|
+
read is left out."""
|
|
68
69
|
files = [(relative, source) for relative, source in _data_files(home, paths)
|
|
69
70
|
if not any(PurePosixPath(relative).is_relative_to(owned) for owned in leaving_out)]
|
|
71
|
+
parts = []
|
|
70
72
|
if write_namespace is not None:
|
|
71
73
|
uploader = NamespaceUploader(write_namespace)
|
|
72
|
-
parts = []
|
|
73
74
|
for relative, source in files:
|
|
74
|
-
|
|
75
|
+
try:
|
|
76
|
+
await uploader.upload(relative, source)
|
|
77
|
+
except (TransferError, OSError):
|
|
78
|
+
if source.exists():
|
|
79
|
+
raise
|
|
80
|
+
continue
|
|
75
81
|
parts.append(uploaded_file_part(relative, name=source.name, mime_type=_mime(source)))
|
|
76
82
|
return parts
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
for relative, source in files:
|
|
84
|
+
try:
|
|
85
|
+
data = source.read_bytes()
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
continue
|
|
88
|
+
parts.append(FilePart(file=FileWithBytes(bytes=base64.b64encode(data).decode("ascii"), name=source.name,
|
|
89
|
+
mimeType=_mime(source)),
|
|
90
|
+
metadata={DATA_OBJECTS_EXTENSION_URI: {"path": relative}}))
|
|
91
|
+
return parts
|
|
83
92
|
|
|
84
93
|
|
|
85
94
|
async def part_bytes(part: FilePart) -> bytes:
|
|
@@ -17,6 +17,10 @@ POLL_S = 0.2
|
|
|
17
17
|
WITH_FDS = Path(__file__).with_name("_with_fds.py")
|
|
18
18
|
#: The processes launched into the session, kept until they exit so each is reaped.
|
|
19
19
|
_running: set[asyncio.Task] = set()
|
|
20
|
+
#: Each launched process's reaping, by pid, until its exit is asked for: its return code, negative for a signal.
|
|
21
|
+
_exits: dict[int, asyncio.Future] = {}
|
|
22
|
+
#: How many exits no one has asked for are kept, the latest.
|
|
23
|
+
KEPT_EXITS = 16
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
async def wait_until_up() -> None:
|
|
@@ -38,12 +42,35 @@ async def launch(argv: list[str], cwd: Path, env: dict[str, str], fds: Optional[
|
|
|
38
42
|
reaped = asyncio.ensure_future(process.wait())
|
|
39
43
|
_running.add(reaped)
|
|
40
44
|
reaped.add_done_callback(_running.discard)
|
|
45
|
+
_exits[process.pid] = reaped
|
|
46
|
+
reaped.add_done_callback(lambda _: _forget_old_exits())
|
|
41
47
|
return process.pid
|
|
42
48
|
|
|
43
49
|
|
|
50
|
+
def _forget_old_exits() -> None:
|
|
51
|
+
"""Drop all but the latest ``KEPT_EXITS`` exits no one has asked for."""
|
|
52
|
+
for pid in [pid for pid, reaped in _exits.items() if reaped.done()][:-KEPT_EXITS]:
|
|
53
|
+
del _exits[pid]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def returncode(pid: int, timeout_s: float) -> Optional[int]:
|
|
57
|
+
"""The return code the process ``launch`` started as ``pid`` exited with, negative for the signal that ended it,
|
|
58
|
+
once it's reaped within ``timeout_s``; None for one ``launch`` didn't start or that hasn't exited."""
|
|
59
|
+
reaped = _exits.get(pid)
|
|
60
|
+
if reaped is None:
|
|
61
|
+
return None
|
|
62
|
+
try:
|
|
63
|
+
code = await asyncio.wait_for(asyncio.shield(reaped), timeout_s)
|
|
64
|
+
except TimeoutError:
|
|
65
|
+
return None
|
|
66
|
+
_exits.pop(pid, None)
|
|
67
|
+
return code
|
|
68
|
+
|
|
69
|
+
|
|
44
70
|
async def stop(pid: int, timeout_s: float) -> None:
|
|
45
71
|
"""End the session ``launch`` started as ``pid``, every process in it: asked to exit, then killed after
|
|
46
|
-
``timeout_s``."""
|
|
72
|
+
``timeout_s``. No one asks how it exited once it's stopped."""
|
|
73
|
+
_exits.pop(pid, None)
|
|
47
74
|
try:
|
|
48
75
|
os.killpg(pid, signal.SIGTERM)
|
|
49
76
|
except ProcessLookupError:
|