agentenv-framework-protocol 0.1.418__tar.gz → 0.1.420__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.418 → agentenv_framework_protocol-0.1.420}/PKG-INFO +19 -5
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/README.md +18 -4
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/agent_env_environment.py +11 -2
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/client.py +6 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/__init__.py +2 -1
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/app.py +142 -25
- agentenv_framework_protocol-0.1.420/src/agentenv_protocol/computer/app_data.py +217 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/app_runtime.py +21 -2
- agentenv_framework_protocol-0.1.420/src/agentenv_protocol/computer/apps.py +125 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/checks.py +14 -2
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/client.py +3 -2
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/dataplane.py +30 -21
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/driver.py +32 -5
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/runtime.py +4 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/serve_app.py +4 -2
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/session.py +33 -1
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_client.py +8 -0
- agentenv_framework_protocol-0.1.420/tests/test_computer_app_data.py +280 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_app_server.py +56 -1
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_conformance.py +23 -2
- agentenv_framework_protocol-0.1.418/src/agentenv_protocol/computer/apps.py +0 -63
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/card.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/errors.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/frames.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/operations.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/platform.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/results.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/computer/tools.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/probe_fake.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_driver.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.418 → agentenv_framework_protocol-0.1.420}/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.420
|
|
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
|
|
@@ -561,8 +561,8 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
|
|
|
561
561
|
or a `transfers.ReadObject` read grant under the marker's `read`, and writes them atomically.
|
|
562
562
|
`data/get` returns the files under the `paths` it's given, relative to the home, or under the
|
|
563
563
|
card's `default_data_paths` (`Desktop`, `Documents`, `Downloads`) when it's given none, inline or
|
|
564
|
-
uploaded under a `write_namespace` grant
|
|
565
|
-
refuses them.
|
|
564
|
+
uploaded under a `write_namespace` grant, leaving out the paths its apps keep their data in. An
|
|
565
|
+
environment whose `@get_data` hook takes no `paths` refuses them.
|
|
566
566
|
- **The card** (the name from `ENVIRONMENT_NAME`, then `@computer_driver`'s), and per-role tool
|
|
567
567
|
access (`tool_access` is on).
|
|
568
568
|
|
|
@@ -730,8 +730,10 @@ class MailApp(ComputerAppEnvironment):
|
|
|
730
730
|
naming one the app declares no install for is refused when the class is read. A step reads
|
|
731
731
|
`self.build_settings`; a deploy's `self.settings` aren't known at build, and reading one raises
|
|
732
732
|
`AttributeError` (`python -m agentenv_protocol.computer.run_install_step <file>:<Class> <method>`).
|
|
733
|
-
- **Its skill** is a file of its folder (`skill = "skill.md"`) or an `@agent_skill` method, not both
|
|
734
|
-
|
|
733
|
+
- **Its skill** is a file of its folder (`skill = "skill.md"`) or an `@agent_skill` method, not both.
|
|
734
|
+
- **Its data** is under the `paths` its OS's decorator names, relative to the session user's home,
|
|
735
|
+
none inside another (`@linux(..., paths=["Mail", ".config/mail/accounts.json"])`), or what its own
|
|
736
|
+
`@add_data`, `@get_data` and `@reset_data` hooks serve instead (below).
|
|
735
737
|
- **`Settings`** is typed; a computer hands each app the values a deploy gives it, as one JSON value
|
|
736
738
|
in `APP_SETTINGS_ENV` (`AGENTENV_APP_SETTINGS`, the apps' values by name), and the base class reads
|
|
737
739
|
its own, by `ENVIRONMENT_NAME`, as `self.settings`. A `SecretStr` field is a secret.
|
|
@@ -757,6 +759,18 @@ folder: the computer is ready once every app's server answers and each `boot` ap
|
|
|
757
759
|
no launch, says it's ready; a `demand` app counts once something launches it. What the server does
|
|
758
760
|
in the session (`agentenv_protocol.computer.session`) is Linux's.
|
|
759
761
|
|
|
762
|
+
The server serves the app's data plane and its skill. With `paths` alone, `data/add` writes the files
|
|
763
|
+
it's given under them and refuses any other path, or one a link leads out of them; `data/get`
|
|
764
|
+
returns the files there, a file tree; and `data/reset` puts them back as the server found them when
|
|
765
|
+
it first started on this boot, which is the app as installed. Hooks serve what they mark instead.
|
|
766
|
+
A hook's `data/add` parts come as they were sent, inline or as a read grant, and
|
|
767
|
+
`part_bytes(part)` returns a part's bytes either way. Either way the base class takes data calls and
|
|
768
|
+
launches one at a time, runs a load and a reset with the app stopped and relaunches it after,
|
|
769
|
+
follows a failed load with a reset, and fails a call that runs past `data_timeout_s` (300 s); a sync
|
|
770
|
+
hook runs in a thread, which the reset and relaunch after it wait for. The driver fronts its apps:
|
|
771
|
+
its card lists each one's card as a child environment, rooted at `/svc/<app>/`, where it forwards
|
|
772
|
+
requests to the app's server.
|
|
773
|
+
|
|
760
774
|
`MailApp.declaration()` returns what the class declares, an `AppDeclaration`, which a `computer_app`
|
|
761
775
|
env stores, and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
|
|
762
776
|
JSON, or a driver's `DriverDeclaration`, without the class's own dependencies installed.
|
|
@@ -505,8 +505,8 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
|
|
|
505
505
|
or a `transfers.ReadObject` read grant under the marker's `read`, and writes them atomically.
|
|
506
506
|
`data/get` returns the files under the `paths` it's given, relative to the home, or under the
|
|
507
507
|
card's `default_data_paths` (`Desktop`, `Documents`, `Downloads`) when it's given none, inline or
|
|
508
|
-
uploaded under a `write_namespace` grant
|
|
509
|
-
refuses them.
|
|
508
|
+
uploaded under a `write_namespace` grant, leaving out the paths its apps keep their data in. An
|
|
509
|
+
environment whose `@get_data` hook takes no `paths` refuses them.
|
|
510
510
|
- **The card** (the name from `ENVIRONMENT_NAME`, then `@computer_driver`'s), and per-role tool
|
|
511
511
|
access (`tool_access` is on).
|
|
512
512
|
|
|
@@ -674,8 +674,10 @@ class MailApp(ComputerAppEnvironment):
|
|
|
674
674
|
naming one the app declares no install for is refused when the class is read. A step reads
|
|
675
675
|
`self.build_settings`; a deploy's `self.settings` aren't known at build, and reading one raises
|
|
676
676
|
`AttributeError` (`python -m agentenv_protocol.computer.run_install_step <file>:<Class> <method>`).
|
|
677
|
-
- **Its skill** is a file of its folder (`skill = "skill.md"`) or an `@agent_skill` method, not both
|
|
678
|
-
|
|
677
|
+
- **Its skill** is a file of its folder (`skill = "skill.md"`) or an `@agent_skill` method, not both.
|
|
678
|
+
- **Its data** is under the `paths` its OS's decorator names, relative to the session user's home,
|
|
679
|
+
none inside another (`@linux(..., paths=["Mail", ".config/mail/accounts.json"])`), or what its own
|
|
680
|
+
`@add_data`, `@get_data` and `@reset_data` hooks serve instead (below).
|
|
679
681
|
- **`Settings`** is typed; a computer hands each app the values a deploy gives it, as one JSON value
|
|
680
682
|
in `APP_SETTINGS_ENV` (`AGENTENV_APP_SETTINGS`, the apps' values by name), and the base class reads
|
|
681
683
|
its own, by `ENVIRONMENT_NAME`, as `self.settings`. A `SecretStr` field is a secret.
|
|
@@ -701,6 +703,18 @@ folder: the computer is ready once every app's server answers and each `boot` ap
|
|
|
701
703
|
no launch, says it's ready; a `demand` app counts once something launches it. What the server does
|
|
702
704
|
in the session (`agentenv_protocol.computer.session`) is Linux's.
|
|
703
705
|
|
|
706
|
+
The server serves the app's data plane and its skill. With `paths` alone, `data/add` writes the files
|
|
707
|
+
it's given under them and refuses any other path, or one a link leads out of them; `data/get`
|
|
708
|
+
returns the files there, a file tree; and `data/reset` puts them back as the server found them when
|
|
709
|
+
it first started on this boot, which is the app as installed. Hooks serve what they mark instead.
|
|
710
|
+
A hook's `data/add` parts come as they were sent, inline or as a read grant, and
|
|
711
|
+
`part_bytes(part)` returns a part's bytes either way. Either way the base class takes data calls and
|
|
712
|
+
launches one at a time, runs a load and a reset with the app stopped and relaunches it after,
|
|
713
|
+
follows a failed load with a reset, and fails a call that runs past `data_timeout_s` (300 s); a sync
|
|
714
|
+
hook runs in a thread, which the reset and relaunch after it wait for. The driver fronts its apps:
|
|
715
|
+
its card lists each one's card as a child environment, rooted at `/svc/<app>/`, where it forwards
|
|
716
|
+
requests to the app's server.
|
|
717
|
+
|
|
704
718
|
`MailApp.declaration()` returns what the class declares, an `AppDeclaration`, which a `computer_app`
|
|
705
719
|
env stores, and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
|
|
706
720
|
JSON, or a driver's `DriverDeclaration`, without the class's own dependencies installed.
|
|
@@ -14,7 +14,8 @@ import sys
|
|
|
14
14
|
import types
|
|
15
15
|
import uuid
|
|
16
16
|
from abc import ABC, abstractmethod
|
|
17
|
-
from typing import Annotated, Any, Callable, ClassVar, Literal, NamedTuple, Union, get_args, get_origin,
|
|
17
|
+
from typing import (Annotated, Any, Callable, ClassVar, Literal, NamedTuple, Optional, Union, get_args, get_origin,
|
|
18
|
+
get_type_hints)
|
|
18
19
|
|
|
19
20
|
from pydantic import TypeAdapter, ValidationError
|
|
20
21
|
from pydantic.fields import FieldInfo
|
|
@@ -376,7 +377,10 @@ class AgentEnvApplication(ABC):
|
|
|
376
377
|
return self.environment_card
|
|
377
378
|
|
|
378
379
|
async def _serve_card(self, request: Request) -> Response:
|
|
379
|
-
|
|
380
|
+
card = self.environment_card
|
|
381
|
+
if isinstance(self.handler, AgentEnvEnvironment) and (children := await self.handler.children_environments()):
|
|
382
|
+
card = card.model_copy(update={"children_environments": children})
|
|
383
|
+
return _json_response(card.model_dump())
|
|
380
384
|
|
|
381
385
|
async def update_tool(self, name: str, *, description: str) -> None:
|
|
382
386
|
"""Change a served tool's description after the mount: on the card and in tools/list alike."""
|
|
@@ -662,6 +666,11 @@ class AgentEnvEnvironment:
|
|
|
662
666
|
"""Declaration-only extensions to advertise, like ``intake/v1``: a promise with params and no handler."""
|
|
663
667
|
return []
|
|
664
668
|
|
|
669
|
+
async def children_environments(self) -> Optional[list[EnvironmentCard]]:
|
|
670
|
+
"""The environments this one fronts, as each serves its card with its paths under this one's, listed on the card
|
|
671
|
+
each time it's served; None for none."""
|
|
672
|
+
return None
|
|
673
|
+
|
|
665
674
|
def served_extensions(self, extensions: list[EnvironmentExtension]) -> list[EnvironmentExtension]:
|
|
666
675
|
"""The ``@extension`` handlers' extensions to serve, chosen at mount: one left out is not served, and of one served
|
|
667
676
|
with ``op``, only the methods its returned descriptor still lists."""
|
|
@@ -147,6 +147,12 @@ def intake_declaration(card: dict) -> dict | None:
|
|
|
147
147
|
return params if params else None
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
def operations(card: dict) -> list[str] | None:
|
|
151
|
+
"""The data-plane methods a card declares it serves (``capabilities.operations``), or None when it declares none,
|
|
152
|
+
as an older card doesn't."""
|
|
153
|
+
return (card.get("capabilities") or {}).get("operations")
|
|
154
|
+
|
|
155
|
+
|
|
150
156
|
def find_tool(card: dict, name: str) -> dict | None:
|
|
151
157
|
"""Return the MCP tool advertised under `name`, or None.
|
|
152
158
|
|
|
@@ -67,6 +67,7 @@ from .operations import (
|
|
|
67
67
|
type_text,
|
|
68
68
|
write_files,
|
|
69
69
|
)
|
|
70
|
+
from .dataplane import part_bytes
|
|
70
71
|
from .keys import PRINTABLE_KEYS
|
|
71
72
|
from .manifest import ManifestSegment, RecordingManifest
|
|
72
73
|
from .platform import Platform
|
|
@@ -107,7 +108,7 @@ __all__ = [
|
|
|
107
108
|
"ComputerAppEnvironment", "computer_app", "install", "linux", "install_step", "ready_check", "Linux", "Service",
|
|
108
109
|
"Launch", "Ready", "Packages", "AptSource", "Download", "RunScript",
|
|
109
110
|
"AppDeclaration", "AppOs",
|
|
110
|
-
"APP_SETTINGS_ENV",
|
|
111
|
+
"APP_SETTINGS_ENV", "part_bytes",
|
|
111
112
|
"Display", "Image", "Effect", "ExecResult", "ReadyCheck", "Platform", "ComputerSettings", "SETTINGS_ENV",
|
|
112
113
|
"PRINTABLE_KEYS",
|
|
113
114
|
"display", "set_display", "capture", "click", "drag", "scroll", "type_text", "press",
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
"""ComputerAppEnvironment: software on a computer, an AgentEnvEnvironment whose class also declares what it is for a
|
|
2
2
|
computer: the platforms it supports and the permissions it needs (``@computer_app``); for each OS, how it installs and
|
|
3
3
|
launches there and what differs there (``@linux``, or ``@install`` for any OS type); its install steps and ready checks,
|
|
4
|
-
each on every OS or on those it names; its skill, its typed ``Settings`` and its ``BuildSettings
|
|
5
|
-
|
|
6
|
-
``
|
|
7
|
-
|
|
4
|
+
each on every OS or on those it names; its skill, its typed ``Settings`` and its ``BuildSettings``; and its data, the
|
|
5
|
+
``paths`` it keeps it in under the session user's home or its own ``@add_data``, ``@get_data`` and ``@reset_data``
|
|
6
|
+
hooks. A ``computer_app`` env's put reads that declaration from the class. Its name is its env's ``environment_name``.
|
|
7
|
+
On the computer the class runs as the app's own server (``serve_app``), which launches the app, adds its launcher, says
|
|
8
|
+
whether it's ready (``computer-app/v1``), serves its skill, and serves its data plane, stopping the app around a load
|
|
9
|
+
and a reset, as declared for the OS it runs on."""
|
|
8
10
|
from __future__ import annotations
|
|
9
11
|
|
|
10
12
|
import functools
|
|
11
13
|
import inspect
|
|
14
|
+
import itertools
|
|
12
15
|
import json
|
|
13
16
|
import os
|
|
14
17
|
from dataclasses import dataclass
|
|
15
18
|
from pathlib import Path, PurePosixPath
|
|
16
19
|
from typing import Annotated, Any, Callable, ClassVar, Literal, Optional, Union
|
|
17
20
|
|
|
18
|
-
from pydantic import AfterValidator, BaseModel, ConfigDict, Field,
|
|
21
|
+
from pydantic import (AfterValidator, BaseModel, ConfigDict, Field, TypeAdapter, ValidationError, create_model,
|
|
22
|
+
field_validator, model_validator)
|
|
19
23
|
|
|
20
|
-
from ..agent_env_environment import _EXT_ATTR, _OP_ATTR, AgentEnvEnvironment,
|
|
21
|
-
|
|
24
|
+
from ..agent_env_environment import (_EXT_ATTR, _OP_ATTR, OP_ADD_DATA, OP_GET_DATA, OP_RESET_DATA, AgentEnvEnvironment,
|
|
25
|
+
add_data, agent_skill, extension, get_data, reset_data)
|
|
26
|
+
from ..transfers import RelativePath, WriteNamespaceGrant
|
|
27
|
+
from ..types import (METHOD_ADD, METHOD_GET, METHOD_RESET, SKILL_EXTENSION_URI, EnvironmentExtension, IntakeDeclaration,
|
|
28
|
+
IntakeFormat, intake_extension)
|
|
29
|
+
from .app_data import AppDataPlane
|
|
22
30
|
from .app_runtime import AppRuntime
|
|
23
31
|
from .errors import InvalidArgument
|
|
24
32
|
from .platform import detect
|
|
@@ -27,7 +35,8 @@ from .platform import detect
|
|
|
27
35
|
APP_SETTINGS_ENV = "AGENTENV_APP_SETTINGS"
|
|
28
36
|
_INSTALL_STEP_ATTR = "_aee_install_step"
|
|
29
37
|
_READY_CHECK_ATTR = "_aee_ready_check"
|
|
30
|
-
_DATA_METHODS = {
|
|
38
|
+
_DATA_METHODS = {OP_RESET_DATA: METHOD_RESET, OP_ADD_DATA: METHOD_ADD, OP_GET_DATA: METHOD_GET}
|
|
39
|
+
_PATHS = TypeAdapter(list[RelativePath])
|
|
31
40
|
#: What a launch's window can be matched by, as the session lists each window.
|
|
32
41
|
WINDOW_KEYS = frozenset({"class", "instance", "title"})
|
|
33
42
|
#: The framework extension an app's own server serves: its launch as params, and its ``launch`` and ``ready`` methods.
|
|
@@ -207,13 +216,15 @@ class AppReadyCheck(BaseModel):
|
|
|
207
216
|
|
|
208
217
|
class AppOs(BaseModel):
|
|
209
218
|
"""What an app declares for one OS type: ``install``, how that OS installs it, as the OS type's install model gives
|
|
210
|
-
it; its ``launch`` there;
|
|
211
|
-
class's, the schemas with the class's
|
|
219
|
+
it; its ``launch`` there; the ``paths`` it keeps its data in there, relative to the session user's home; and its
|
|
220
|
+
``skill``, ``settings`` and ``build_settings`` there when they differ from the class's, the schemas with the class's
|
|
221
|
+
fields added."""
|
|
212
222
|
|
|
213
223
|
model_config = ConfigDict(extra="forbid")
|
|
214
224
|
|
|
215
225
|
install: dict[str, Any]
|
|
216
226
|
launch: Optional[Launch] = None
|
|
227
|
+
paths: Optional[list[str]] = None
|
|
217
228
|
skill: Optional[AppSkill] = None
|
|
218
229
|
settings: Optional[dict[str, Any]] = None
|
|
219
230
|
build_settings: Optional[dict[str, Any]] = None
|
|
@@ -258,6 +269,7 @@ class _OsDeclared:
|
|
|
258
269
|
|
|
259
270
|
install: BaseModel
|
|
260
271
|
launch: Optional[Launch]
|
|
272
|
+
paths: tuple[str, ...]
|
|
261
273
|
skill: Optional[str]
|
|
262
274
|
settings: Optional[type[BaseModel]]
|
|
263
275
|
build_settings: Optional[type[BaseModel]]
|
|
@@ -274,17 +286,26 @@ def computer_app(*, supports: Optional[list] = None, permissions: Optional[list[
|
|
|
274
286
|
return deco
|
|
275
287
|
|
|
276
288
|
|
|
277
|
-
def install(os_type: str, spec: BaseModel, *, launch: Optional[Launch] = None,
|
|
278
|
-
settings: Optional[type[BaseModel]] = None,
|
|
289
|
+
def install(os_type: str, spec: BaseModel, *, launch: Optional[Launch] = None, paths: Optional[list[str]] = None,
|
|
290
|
+
skill: Optional[str] = None, settings: Optional[type[BaseModel]] = None,
|
|
279
291
|
build_settings: Optional[type[BaseModel]] = None) -> Callable:
|
|
280
292
|
"""Declare how a computer of ``os_type`` installs the app, ``spec`` being that OS type's install model, and what is
|
|
281
|
-
the app's own there: its ``launch``;
|
|
282
|
-
|
|
283
|
-
|
|
293
|
+
the app's own there: its ``launch``; the ``paths`` it keeps its data in, relative to the session user's home, which
|
|
294
|
+
serve its data plane when it has no data hooks; its ``skill``, a file of its folder, instead of the class's; and
|
|
295
|
+
``settings`` and ``build_settings``, models whose fields add to the class's ``Settings`` and ``BuildSettings`` there.
|
|
296
|
+
``@linux`` is this for Linux; an OS type a plugin adds is declared with it."""
|
|
297
|
+
try:
|
|
298
|
+
owned = tuple(_PATHS.validate_python(list(paths or [])))
|
|
299
|
+
except ValidationError:
|
|
300
|
+
raise ValueError(f"paths are normalized paths relative to the session user's home, not {paths!r}") from None
|
|
301
|
+
if overlapping := [f"{a!r} and {b!r}" for a, b in itertools.combinations(owned, 2)
|
|
302
|
+
if PurePosixPath(a).is_relative_to(b) or PurePosixPath(b).is_relative_to(a)]:
|
|
303
|
+
raise ValueError(f"an app's paths each hold files of their own, but {'; '.join(overlapping)} overlap")
|
|
304
|
+
|
|
284
305
|
def deco(cls: type) -> type:
|
|
285
306
|
if not (isinstance(cls, type) and issubclass(cls, ComputerAppEnvironment)):
|
|
286
307
|
raise TypeError("@install decorates a ComputerAppEnvironment subclass")
|
|
287
|
-
cls._installs = {**cls._installs, os_type: _OsDeclared(spec, launch, skill, settings, build_settings)}
|
|
308
|
+
cls._installs = {**cls._installs, os_type: _OsDeclared(spec, launch, owned, skill, settings, build_settings)}
|
|
288
309
|
return cls
|
|
289
310
|
return deco
|
|
290
311
|
|
|
@@ -292,12 +313,13 @@ def install(os_type: str, spec: BaseModel, *, launch: Optional[Launch] = None, s
|
|
|
292
313
|
def linux(*, files: str = ".", packages: Optional[Packages] = None, python: Optional[str] = None,
|
|
293
314
|
sources: Optional[list[AptSource]] = None, downloads: Optional[list[Download]] = None,
|
|
294
315
|
run: Optional[list[RunScript]] = None, services: Optional[list[Service]] = None,
|
|
295
|
-
ports: Optional[list[int]] = None, launch: Optional[Launch] = None,
|
|
296
|
-
|
|
316
|
+
ports: Optional[list[int]] = None, launch: Optional[Launch] = None, paths: Optional[list[str]] = None,
|
|
317
|
+
skill: Optional[str] = None, settings: Optional[type[BaseModel]] = None,
|
|
318
|
+
build_settings: Optional[type[BaseModel]] = None) -> Callable:
|
|
297
319
|
"""``@install`` for Linux: how a Linux computer installs the app, ``Linux``'s fields, and what is its own there."""
|
|
298
320
|
return install("linux", Linux(files=files, packages=packages, python=python, sources=sources, downloads=downloads,
|
|
299
321
|
run=run, services=services or [], ports=ports),
|
|
300
|
-
launch=launch, skill=skill, settings=settings, build_settings=build_settings)
|
|
322
|
+
launch=launch, paths=paths, skill=skill, settings=settings, build_settings=build_settings)
|
|
301
323
|
|
|
302
324
|
|
|
303
325
|
def install_step(scope: Literal["system", "session"] = "system", *,
|
|
@@ -328,15 +350,41 @@ def _named(os_types: Union[str, list[str], None]) -> Optional[list[str]]:
|
|
|
328
350
|
class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
329
351
|
"""Subclass it, declare it with ``@computer_app`` and an OS decorator for each OS (``@linux``), and set ``skill`` as
|
|
330
352
|
a class attribute; a deploy's settings arrive as ``self.settings``, those its env fixes at build as
|
|
331
|
-
``self.build_settings``, and its launch as ``self.launch``, each as declared for the OS
|
|
353
|
+
``self.build_settings``, and its launch and paths as ``self.launch`` and ``self.paths``, each as declared for the OS
|
|
354
|
+
it runs on.
|
|
355
|
+
|
|
356
|
+
Its data plane is its ``@add_data``, ``@get_data`` and ``@reset_data`` hooks, or, with none, defaults on its
|
|
357
|
+
``paths``, which the base class serves around its contract (``app_data.AppDataPlane``): one call at a time, a load
|
|
358
|
+
and a reset with the app stopped and relaunched after, a load that fails followed by a reset, and each call within
|
|
359
|
+
``data_timeout_s``."""
|
|
332
360
|
|
|
333
361
|
supports: ClassVar[list] = []
|
|
334
362
|
permissions: ClassVar[list[str]] = []
|
|
335
363
|
_installs: ClassVar[dict[str, _OsDeclared]] = {}
|
|
364
|
+
#: Its data hooks' method names, by the operation each serves.
|
|
365
|
+
_data_hooks: ClassVar[dict[str, str]] = {}
|
|
336
366
|
_computer_app: Optional[AppRuntime] = None
|
|
367
|
+
_computer_app_data: Optional[AppDataPlane] = None
|
|
337
368
|
launch: Optional[Launch] = None
|
|
369
|
+
paths: list[str] = []
|
|
338
370
|
#: A file of the app's folder holding its skill for agents, when no ``@agent_skill`` method writes it.
|
|
339
371
|
skill: ClassVar[Optional[str]] = None
|
|
372
|
+
#: How long one of its data calls may take, a hook or its paths' default, before it fails.
|
|
373
|
+
data_timeout_s: ClassVar[float] = 300.0
|
|
374
|
+
|
|
375
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
376
|
+
"""Take the class's data hooks over, for the base class's handlers to serve around its contract; and serve its
|
|
377
|
+
skill file unless it writes its skill itself."""
|
|
378
|
+
super().__init_subclass__(**kwargs)
|
|
379
|
+
hooks = dict(cls._data_hooks)
|
|
380
|
+
for name, member in vars(cls).items():
|
|
381
|
+
if (op := getattr(member, _OP_ATTR, None)) in _DATA_METHODS:
|
|
382
|
+
hooks[op] = name
|
|
383
|
+
delattr(member, _OP_ATTR)
|
|
384
|
+
cls._data_hooks = hooks
|
|
385
|
+
if any(getattr(getattr(member, _EXT_ATTR, None), "uri", None) == SKILL_EXTENSION_URI
|
|
386
|
+
for member in vars(cls).values()):
|
|
387
|
+
cls._computer_app_skill = None
|
|
340
388
|
|
|
341
389
|
class Settings(BaseModel):
|
|
342
390
|
"""The app's settings: an app declares its own; a ``SecretStr`` field is a secret."""
|
|
@@ -350,12 +398,17 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
350
398
|
values = json.loads(os.environ.get(APP_SETTINGS_ENV) or "{}").get(os.environ.get("ENVIRONMENT_NAME"), {})
|
|
351
399
|
self.settings = cls.settings_on(on).model_validate(values)
|
|
352
400
|
self.build_settings = _built_settings(cls, on)
|
|
353
|
-
|
|
401
|
+
declared = cls._installs.get(on)
|
|
402
|
+
self.launch = declared.launch if declared is not None else None
|
|
403
|
+
self.paths = list(declared.paths) if declared is not None else []
|
|
354
404
|
ready = self.launch.ready if self.launch is not None else None
|
|
355
405
|
self._computer_app = AppRuntime(
|
|
356
406
|
self.launch, {fn.__name__: getattr(self, fn.__name__) for fn in _methods(cls)
|
|
357
407
|
if hasattr(fn, _READY_CHECK_ATTR) and _runs_on(getattr(fn, _READY_CHECK_ATTR), on)},
|
|
358
408
|
ready.timeout_s if ready is not None else DEFAULT_TIMEOUT_S)
|
|
409
|
+
self._computer_app_data = AppDataPlane(
|
|
410
|
+
self._computer_app, {op: getattr(self, name) for op, name in cls._data_hooks.items()}, self.paths,
|
|
411
|
+
cls.data_timeout_s)
|
|
359
412
|
|
|
360
413
|
@classmethod
|
|
361
414
|
def settings_on(cls, os_type: Optional[str]) -> type[BaseModel]:
|
|
@@ -375,7 +428,7 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
375
428
|
async def _computer_app_launch(self) -> dict:
|
|
376
429
|
if self.launch is None:
|
|
377
430
|
raise InvalidArgument(f"{type(self).__name__} declares no launch")
|
|
378
|
-
return {"pid": await self.
|
|
431
|
+
return {"pid": await self._computer_app_data_plane().launch()}
|
|
379
432
|
|
|
380
433
|
@extension(COMPUTER_APP, op="ready", method="GET")
|
|
381
434
|
async def _computer_app_ready(self) -> dict:
|
|
@@ -386,11 +439,61 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
386
439
|
raise TypeError(f"{type(self).__qualname__}.__init__ must call super().__init__()")
|
|
387
440
|
return self._computer_app
|
|
388
441
|
|
|
442
|
+
def _computer_app_data_plane(self) -> AppDataPlane:
|
|
443
|
+
self._computer_app_runtime()
|
|
444
|
+
return self._computer_app_data
|
|
445
|
+
|
|
389
446
|
def extension_params(self, uri: str) -> dict:
|
|
390
447
|
if uri == COMPUTER_APP:
|
|
391
|
-
return {"launch": self.launch.model_dump(mode="json", exclude_none=True) if self.launch else None
|
|
448
|
+
return {"launch": self.launch.model_dump(mode="json", exclude_none=True) if self.launch else None,
|
|
449
|
+
"paths": self.paths}
|
|
392
450
|
return super().extension_params(uri)
|
|
393
451
|
|
|
452
|
+
def served_operations(self, operations: list[str]) -> list[str]:
|
|
453
|
+
"""Its hooks' operations, or with none, all three when it has paths."""
|
|
454
|
+
served = ({_DATA_METHODS[op] for op in self._data_hooks} if self._data_hooks
|
|
455
|
+
else set(_DATA_METHODS.values()) if self.paths else set())
|
|
456
|
+
return [op for op in operations if op in served]
|
|
457
|
+
|
|
458
|
+
def served_extensions(self, extensions: list[EnvironmentExtension]) -> list[EnvironmentExtension]:
|
|
459
|
+
"""Without its skill file on this OS, no skill, unless it writes one itself."""
|
|
460
|
+
own_skill = getattr(type(self), "_computer_app_skill", None) is not None
|
|
461
|
+
if own_skill and self._computer_app_skill_file() is None:
|
|
462
|
+
extensions = [extension for extension in extensions if extension.uri != SKILL_EXTENSION_URI]
|
|
463
|
+
return super().served_extensions(extensions)
|
|
464
|
+
|
|
465
|
+
def declared_extensions(self) -> list[EnvironmentExtension]:
|
|
466
|
+
"""With its paths serving its data, a file tree: ``data/get`` returns a part per file."""
|
|
467
|
+
declared = super().declared_extensions()
|
|
468
|
+
if self.paths and not self._data_hooks:
|
|
469
|
+
tree = IntakeDeclaration(get=[IntakeFormat(part="file", format="file-tree")],
|
|
470
|
+
add=[IntakeFormat(part="file", format="file-tree", load="additive")])
|
|
471
|
+
declared.append(intake_extension(tree, f"data/add writes files under the app's paths, "
|
|
472
|
+
f"{', '.join(self.paths)}; data/get returns them; data/reset puts "
|
|
473
|
+
"them back as installed."))
|
|
474
|
+
return declared
|
|
475
|
+
|
|
476
|
+
@add_data
|
|
477
|
+
async def _computer_app_add_data(self, parts: list) -> None:
|
|
478
|
+
await self._computer_app_data_plane().call(OP_ADD_DATA, parts)
|
|
479
|
+
|
|
480
|
+
@get_data
|
|
481
|
+
async def _computer_app_get_data(self, write_namespace: Optional[WriteNamespaceGrant] = None) -> list:
|
|
482
|
+
return await self._computer_app_data_plane().call(OP_GET_DATA, write_namespace)
|
|
483
|
+
|
|
484
|
+
@reset_data
|
|
485
|
+
async def _computer_app_reset_data(self) -> None:
|
|
486
|
+
await self._computer_app_data_plane().call(OP_RESET_DATA)
|
|
487
|
+
|
|
488
|
+
@agent_skill
|
|
489
|
+
def _computer_app_skill(self) -> tuple[str, str]:
|
|
490
|
+
return _skill_parts(Path(self._computer_app_skill_file()).read_text(), os.environ.get("ENVIRONMENT_NAME", ""))
|
|
491
|
+
|
|
492
|
+
def _computer_app_skill_file(self) -> Optional[str]:
|
|
493
|
+
"""The file of the app's folder holding its skill on this OS, or the class's."""
|
|
494
|
+
declared = type(self)._installs.get(detect().os)
|
|
495
|
+
return declared.skill if declared is not None and declared.skill is not None else type(self).skill
|
|
496
|
+
|
|
394
497
|
@classmethod
|
|
395
498
|
def declaration(cls) -> AppDeclaration:
|
|
396
499
|
"""What the class declares. Raises ValueError for a skill declared twice, a build setting that's a secret, or
|
|
@@ -404,7 +507,8 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
404
507
|
"it in Settings")
|
|
405
508
|
methods = _methods(cls)
|
|
406
509
|
skill_methods = [fn.__name__ for fn in methods
|
|
407
|
-
if getattr(getattr(fn, _EXT_ATTR, None), "uri", None) == SKILL_EXTENSION_URI
|
|
510
|
+
if getattr(getattr(fn, _EXT_ATTR, None), "uri", None) == SKILL_EXTENSION_URI
|
|
511
|
+
and not fn.__name__.startswith("_computer_app_")]
|
|
408
512
|
if cls.skill is not None and skill_methods:
|
|
409
513
|
raise ValueError(f"{cls.__name__} declares its skill twice: skill = {cls.skill!r} and @agent_skill "
|
|
410
514
|
f"{skill_methods[0]}")
|
|
@@ -425,6 +529,7 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
425
529
|
supports=cls.supports, permissions=cls.permissions,
|
|
426
530
|
os={os_type: AppOs(
|
|
427
531
|
install=declared.install.model_dump(mode="json", exclude_none=True), launch=declared.launch,
|
|
532
|
+
paths=list(declared.paths) or None,
|
|
428
533
|
skill=AppSkill(file=declared.skill) if declared.skill is not None else None,
|
|
429
534
|
settings=cls.settings_on(os_type).model_json_schema() if declared.settings is not None else None,
|
|
430
535
|
build_settings=(cls.build_settings_on(os_type).model_json_schema()
|
|
@@ -433,11 +538,23 @@ class ComputerAppEnvironment(AgentEnvEnvironment):
|
|
|
433
538
|
settings=cls.Settings.model_json_schema(), build_settings=build if build.get("properties") else None,
|
|
434
539
|
skill=AppSkill(file=cls.skill) if cls.skill is not None else
|
|
435
540
|
AppSkill(method=skill_methods[0]) if skill_methods else None,
|
|
436
|
-
data=sorted(
|
|
541
|
+
data=sorted(_DATA_METHODS[op] for op in cls._data_hooks),
|
|
437
542
|
install_steps=steps, ready_checks=checks,
|
|
438
543
|
)
|
|
439
544
|
|
|
440
545
|
|
|
546
|
+
def _skill_parts(text: str, name: str) -> tuple[str, str]:
|
|
547
|
+
"""A skill file's description and body: from its frontmatter's ``description`` when it has one."""
|
|
548
|
+
if text.startswith("---\n") and (end := text.find("\n---", 4)) != -1:
|
|
549
|
+
front, body = text[4:end], text[end + 4:].lstrip("\n")
|
|
550
|
+
description = next((line.partition(":")[2].strip().strip("'\"") for line in front.splitlines()
|
|
551
|
+
if line.startswith("description:")), "")
|
|
552
|
+
if description:
|
|
553
|
+
return description, body
|
|
554
|
+
text = body
|
|
555
|
+
return f"How to use {name} on this computer.", text
|
|
556
|
+
|
|
557
|
+
|
|
441
558
|
def _secret_at(schema: dict, path: str = "") -> Optional[str]:
|
|
442
559
|
"""Where in ``schema``, a JSON schema, a secret is, a ``SecretStr``'s ``writeOnly``: in it or any of its subschemas,
|
|
443
560
|
a field's, a list's items or a nested model's definition; None when it holds none. A default or a field's name
|