agentenv-framework-protocol 0.1.443__tar.gz → 0.1.445__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.443 → agentenv_framework_protocol-0.1.445}/.gitignore +1 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/PKG-INFO +37 -11
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/README.md +36 -10
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/__init__.py +18 -3
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/app.py +2 -1
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/apps.py +32 -4
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/card.py +9 -1
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/checks.py +2 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/driver.py +4 -1
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/errors.py +4 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/extensions.py +6 -2
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/frames.py +29 -3
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/operations.py +58 -2
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/platform.py +3 -2
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/results.py +31 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/runtime.py +26 -9
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/tools.py +90 -2
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/website_browser.py +300 -30
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_driver.py +17 -1
- agentenv_framework_protocol-0.1.445/tests/test_computer_page.py +165 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_website_browser.py +337 -20
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/client.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/app_data.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/app_runtime.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/session.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/probe_fake.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_client.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_app_server.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.443 → agentenv_framework_protocol-0.1.445}/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.445
|
|
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
|
|
@@ -529,6 +529,7 @@ pixels), at the display's size times its scale. Input operations may return an `
|
|
|
529
529
|
| `@set_display` | no, with `display_modes` | `computer-display/v1` `set` (`get` is always served) |
|
|
530
530
|
| `@pointer_move`, `@pointer_down`, `@pointer_up`, `@pointer_position` | all or none | `computer-pointer/v1` and its five tools; click and scroll at the pointer |
|
|
531
531
|
| `@key_down`, `@key_up` | both or none | `computer-keyboard/v1` and its two tools |
|
|
532
|
+
| `@navigate`, `@history`, `@tabs`, `@dialog`, `@upload`, `@page_state` | all or none, for a screen that is a website browser's page | `computer-page/v1` and its five tools; the `page` of every frame |
|
|
532
533
|
| `@run_in_session` | no; a Linux default | `computer-exec/v1` |
|
|
533
534
|
| `@write_files` | no; a Linux default | `data/add` |
|
|
534
535
|
| `@record` | no | the stream `computer-recording/v1` records from; without it the base class polls `@capture` |
|
|
@@ -553,15 +554,24 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
|
|
|
553
554
|
group tools. Every result is structured, with a one-line summary as its text block; input results
|
|
554
555
|
carry `applied`, `delivery` and `others_since_frame`, and `screenshot: true` returns a fresh frame
|
|
555
556
|
after acting. Click and scroll leave out `x` and `y` to act at the pointer on a computer with
|
|
556
|
-
`computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`.
|
|
557
|
+
`computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`. With the page
|
|
558
|
+
group, `computer_navigate` opens a URL in the active tab, `computer_history` goes `back`, `forward`
|
|
559
|
+
or `reload`s, `computer_tabs` lists, opens, switches and closes tabs by the browser's own ids and
|
|
560
|
+
answers with the tabs after it, `computer_dialog` answers the page's open dialog and
|
|
561
|
+
`computer_upload` gives its open file chooser files in `~/Uploads`.
|
|
557
562
|
- **Frames and spaces.** Points are pixels of a named frame (your latest by default), or a
|
|
558
563
|
0..`norm_scale` grid over it with `space: "norm"`. A point outside the frame or the range is
|
|
559
564
|
refused (`out_of_bounds`), never clamped. The last 64 frames are kept, and a frame goes stale
|
|
560
|
-
(`stale_frame`) when the display geometry it was captured at changes.
|
|
565
|
+
(`stale_frame`) when the display geometry it was captured at changes. With the page group, every
|
|
566
|
+
frame carries `page`: the active tab's `tab`, `url` and `title`, and the `dialog` (`type`, `message`,
|
|
567
|
+
`default_text`) or `file_chooser` (`multiple`) open on it, which its summary names too.
|
|
561
568
|
- **The input queue.** Input calls run one at a time in arrival order; one that waits past
|
|
562
|
-
`queue_wait_s` is refused `input_busy`. Screenshots and waits never queue.
|
|
569
|
+
`queue_wait_s` is refused `input_busy`. Screenshots and waits never queue. While a dialog is open
|
|
570
|
+
on the page, input it blocks (all but `computer_tabs`, `computer_dialog` and `computer_upload`) is
|
|
571
|
+
refused `dialog_open`, with `applied: "no"`.
|
|
563
572
|
- **The computer skill.** `computer/v1` declares the seven tools, its params the driver's
|
|
564
|
-
features
|
|
573
|
+
features, among them `screen`: `desktop`, or `page` for a driver whose screen is a website
|
|
574
|
+
browser's active tab, which a computer env pairs with exactly one website browser. The driver serves its skill through `skill/v1` (an `@agent_skill` handler on the base
|
|
565
575
|
class): a SKILL.md written from the computer as it booted (its platform, session user and home,
|
|
566
576
|
the live screen size, what its input takes, its OS's shortcuts) and ending with the driver
|
|
567
577
|
class's `notes`, markdown for what its card can't say. A driver whose software ships a skill of
|
|
@@ -569,7 +579,9 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
|
|
|
569
579
|
computer skill leads with it, the computer's part following.
|
|
570
580
|
- **Framework extensions.** `computer-ready/v1` (`status`: real checks of the display and a
|
|
571
581
|
capture, the driver's own `ready_checks()`, and one per app on the computer, asked of the app's
|
|
572
|
-
own server (see Computer apps); tools refuse `device_unavailable` until they pass once
|
|
582
|
+
own server (see Computer apps); tools refuse `device_unavailable` until they pass once; a check
|
|
583
|
+
that can't pass again is `final`, and while one fails the status carries `failed`, saying why,
|
|
584
|
+
which `boot.ready()` raises at once rather than waiting out its timeout), `computer-display/v1`, `computer-spaces/v1` (the default space, written into the tool
|
|
573
585
|
descriptions), `computer-keyboard/v1`, `computer-exec/v1` (`run {argv, cwd?, env?, timeout_s?,
|
|
574
586
|
request_id}`: output capped per stream, a repeated `request_id` answered with the first result) and,
|
|
575
587
|
with `recording`, `computer-recording/v1` (below).
|
|
@@ -798,7 +810,7 @@ its card lists each one's card as a child environment, rooted at `/svc/<app>/`,
|
|
|
798
810
|
requests to the app's server.
|
|
799
811
|
|
|
800
812
|
`MailApp.declaration()` returns what the class declares, an `AppDeclaration`, which a `computer_app`
|
|
801
|
-
env stores, and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
|
|
813
|
+
env stores (`browser: true` for a website browser), and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
|
|
802
814
|
JSON, or a driver's `DriverDeclaration`, without the class's own dependencies installed.
|
|
803
815
|
|
|
804
816
|
### Website browsers
|
|
@@ -825,8 +837,8 @@ are the profile's template. The base class adds:
|
|
|
825
837
|
`AGENTENV_SITE_PROXY` set, it goes out through that proxy, with WebRTC's non-proxied UDP off.
|
|
826
838
|
Alone it bypasses nothing, loopback included; in a computer it bypasses loopback (`127.0.0.1`,
|
|
827
839
|
`[::1]`, `localhost`), so the apps beside it stay reachable while `*.localhost` names still go
|
|
828
|
-
through. Alone
|
|
829
|
-
|
|
840
|
+
through. Alone, on a computer with no display (`XDG_SESSION_TYPE=none`, a browser computer's), it
|
|
841
|
+
also takes `--window-size` and `--headless=new`, and refuses to start with no proxy.
|
|
830
842
|
`declaration()` refuses a subclass whose command carries any of these, or `--remote-allow-origins`, or
|
|
831
843
|
any other way to open a debugging port.
|
|
832
844
|
- **Before each launch** it empties its cache, so no launch inherits another's, and writes the deploy's
|
|
@@ -852,14 +864,28 @@ are the profile's template. The base class adds:
|
|
|
852
864
|
each as of its page's last load, with `closed_at` and
|
|
853
865
|
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
854
866
|
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
855
|
-
and, completed, its size and sha256; and `dialogs`, each with its type, message,
|
|
867
|
+
and, completed, its size and sha256; and `dialogs`, each with its type, message, default text, answer
|
|
868
|
+
and who gave it.
|
|
856
869
|
`hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
|
|
857
870
|
- **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
|
|
858
871
|
`record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
|
|
859
872
|
launch (with its cause: `boot`, `server_restart`, `launch`, `data/add` or `data/reset`), each stop it made
|
|
860
873
|
and each exit it didn't, with its exit `code` or the `signal` that ended it, since the computer booted.
|
|
861
874
|
Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
|
|
862
|
-
failure, its ready check false.
|
|
875
|
+
failure, its ready check false. `page/viewport`, `page/capture` and `page/input`, framework-only, are
|
|
876
|
+
its active tab's page as a page driver sees it: its size, which headless is its window's; a PNG of it,
|
|
877
|
+
or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet), its last capture while
|
|
878
|
+
a dialog keeps it from painting; and CDP input calls (`Input.dispatchMouseEvent`,
|
|
879
|
+
`Input.dispatchKeyEvent`, `Input.insertText`), sent to it in order. Also framework-only, for page
|
|
880
|
+
drivers and agentenv's own steps alike:
|
|
881
|
+
- `navigate {url}` and `history {direction}` (`back`, `forward` or `reload`), in the active tab;
|
|
882
|
+
- `tabs/list`, `tabs/open {url?}`, `tabs/activate {tab}` and `tabs/close {tab}`, by the browser's own tab
|
|
883
|
+
ids. The active tab is the one a dialog is open on, else the one whose page has focus, else the one
|
|
884
|
+
agentenv last brought to the front;
|
|
885
|
+
- `dialog/answer {accept, text?}`, the agent's answer to the dialog open on the active tab;
|
|
886
|
+
- `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
|
|
887
|
+
alone, it intercepts;
|
|
888
|
+
- `page/state`, the active tab's id, URL and title and the dialog or file chooser open on it.
|
|
863
889
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
864
890
|
`~/Downloads`, folders it makes.
|
|
865
891
|
|
|
@@ -473,6 +473,7 @@ pixels), at the display's size times its scale. Input operations may return an `
|
|
|
473
473
|
| `@set_display` | no, with `display_modes` | `computer-display/v1` `set` (`get` is always served) |
|
|
474
474
|
| `@pointer_move`, `@pointer_down`, `@pointer_up`, `@pointer_position` | all or none | `computer-pointer/v1` and its five tools; click and scroll at the pointer |
|
|
475
475
|
| `@key_down`, `@key_up` | both or none | `computer-keyboard/v1` and its two tools |
|
|
476
|
+
| `@navigate`, `@history`, `@tabs`, `@dialog`, `@upload`, `@page_state` | all or none, for a screen that is a website browser's page | `computer-page/v1` and its five tools; the `page` of every frame |
|
|
476
477
|
| `@run_in_session` | no; a Linux default | `computer-exec/v1` |
|
|
477
478
|
| `@write_files` | no; a Linux default | `data/add` |
|
|
478
479
|
| `@record` | no | the stream `computer-recording/v1` records from; without it the base class polls `@capture` |
|
|
@@ -497,15 +498,24 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
|
|
|
497
498
|
group tools. Every result is structured, with a one-line summary as its text block; input results
|
|
498
499
|
carry `applied`, `delivery` and `others_since_frame`, and `screenshot: true` returns a fresh frame
|
|
499
500
|
after acting. Click and scroll leave out `x` and `y` to act at the pointer on a computer with
|
|
500
|
-
`computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`.
|
|
501
|
+
`computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`. With the page
|
|
502
|
+
group, `computer_navigate` opens a URL in the active tab, `computer_history` goes `back`, `forward`
|
|
503
|
+
or `reload`s, `computer_tabs` lists, opens, switches and closes tabs by the browser's own ids and
|
|
504
|
+
answers with the tabs after it, `computer_dialog` answers the page's open dialog and
|
|
505
|
+
`computer_upload` gives its open file chooser files in `~/Uploads`.
|
|
501
506
|
- **Frames and spaces.** Points are pixels of a named frame (your latest by default), or a
|
|
502
507
|
0..`norm_scale` grid over it with `space: "norm"`. A point outside the frame or the range is
|
|
503
508
|
refused (`out_of_bounds`), never clamped. The last 64 frames are kept, and a frame goes stale
|
|
504
|
-
(`stale_frame`) when the display geometry it was captured at changes.
|
|
509
|
+
(`stale_frame`) when the display geometry it was captured at changes. With the page group, every
|
|
510
|
+
frame carries `page`: the active tab's `tab`, `url` and `title`, and the `dialog` (`type`, `message`,
|
|
511
|
+
`default_text`) or `file_chooser` (`multiple`) open on it, which its summary names too.
|
|
505
512
|
- **The input queue.** Input calls run one at a time in arrival order; one that waits past
|
|
506
|
-
`queue_wait_s` is refused `input_busy`. Screenshots and waits never queue.
|
|
513
|
+
`queue_wait_s` is refused `input_busy`. Screenshots and waits never queue. While a dialog is open
|
|
514
|
+
on the page, input it blocks (all but `computer_tabs`, `computer_dialog` and `computer_upload`) is
|
|
515
|
+
refused `dialog_open`, with `applied: "no"`.
|
|
507
516
|
- **The computer skill.** `computer/v1` declares the seven tools, its params the driver's
|
|
508
|
-
features
|
|
517
|
+
features, among them `screen`: `desktop`, or `page` for a driver whose screen is a website
|
|
518
|
+
browser's active tab, which a computer env pairs with exactly one website browser. The driver serves its skill through `skill/v1` (an `@agent_skill` handler on the base
|
|
509
519
|
class): a SKILL.md written from the computer as it booted (its platform, session user and home,
|
|
510
520
|
the live screen size, what its input takes, its OS's shortcuts) and ending with the driver
|
|
511
521
|
class's `notes`, markdown for what its card can't say. A driver whose software ships a skill of
|
|
@@ -513,7 +523,9 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
|
|
|
513
523
|
computer skill leads with it, the computer's part following.
|
|
514
524
|
- **Framework extensions.** `computer-ready/v1` (`status`: real checks of the display and a
|
|
515
525
|
capture, the driver's own `ready_checks()`, and one per app on the computer, asked of the app's
|
|
516
|
-
own server (see Computer apps); tools refuse `device_unavailable` until they pass once
|
|
526
|
+
own server (see Computer apps); tools refuse `device_unavailable` until they pass once; a check
|
|
527
|
+
that can't pass again is `final`, and while one fails the status carries `failed`, saying why,
|
|
528
|
+
which `boot.ready()` raises at once rather than waiting out its timeout), `computer-display/v1`, `computer-spaces/v1` (the default space, written into the tool
|
|
517
529
|
descriptions), `computer-keyboard/v1`, `computer-exec/v1` (`run {argv, cwd?, env?, timeout_s?,
|
|
518
530
|
request_id}`: output capped per stream, a repeated `request_id` answered with the first result) and,
|
|
519
531
|
with `recording`, `computer-recording/v1` (below).
|
|
@@ -742,7 +754,7 @@ its card lists each one's card as a child environment, rooted at `/svc/<app>/`,
|
|
|
742
754
|
requests to the app's server.
|
|
743
755
|
|
|
744
756
|
`MailApp.declaration()` returns what the class declares, an `AppDeclaration`, which a `computer_app`
|
|
745
|
-
env stores, and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
|
|
757
|
+
env stores (`browser: true` for a website browser), and `python -m agentenv_protocol.computer._declare <folder> <file> <class>` prints it as
|
|
746
758
|
JSON, or a driver's `DriverDeclaration`, without the class's own dependencies installed.
|
|
747
759
|
|
|
748
760
|
### Website browsers
|
|
@@ -769,8 +781,8 @@ are the profile's template. The base class adds:
|
|
|
769
781
|
`AGENTENV_SITE_PROXY` set, it goes out through that proxy, with WebRTC's non-proxied UDP off.
|
|
770
782
|
Alone it bypasses nothing, loopback included; in a computer it bypasses loopback (`127.0.0.1`,
|
|
771
783
|
`[::1]`, `localhost`), so the apps beside it stay reachable while `*.localhost` names still go
|
|
772
|
-
through. Alone
|
|
773
|
-
|
|
784
|
+
through. Alone, on a computer with no display (`XDG_SESSION_TYPE=none`, a browser computer's), it
|
|
785
|
+
also takes `--window-size` and `--headless=new`, and refuses to start with no proxy.
|
|
774
786
|
`declaration()` refuses a subclass whose command carries any of these, or `--remote-allow-origins`, or
|
|
775
787
|
any other way to open a debugging port.
|
|
776
788
|
- **Before each launch** it empties its cache, so no launch inherits another's, and writes the deploy's
|
|
@@ -796,14 +808,28 @@ are the profile's template. The base class adds:
|
|
|
796
808
|
each as of its page's last load, with `closed_at` and
|
|
797
809
|
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
798
810
|
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
799
|
-
and, completed, its size and sha256; and `dialogs`, each with its type, message,
|
|
811
|
+
and, completed, its size and sha256; and `dialogs`, each with its type, message, default text, answer
|
|
812
|
+
and who gave it.
|
|
800
813
|
`hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
|
|
801
814
|
- **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
|
|
802
815
|
`record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
|
|
803
816
|
launch (with its cause: `boot`, `server_restart`, `launch`, `data/add` or `data/reset`), each stop it made
|
|
804
817
|
and each exit it didn't, with its exit `code` or the `signal` that ended it, since the computer booted.
|
|
805
818
|
Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
|
|
806
|
-
failure, its ready check false.
|
|
819
|
+
failure, its ready check false. `page/viewport`, `page/capture` and `page/input`, framework-only, are
|
|
820
|
+
its active tab's page as a page driver sees it: its size, which headless is its window's; a PNG of it,
|
|
821
|
+
or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet), its last capture while
|
|
822
|
+
a dialog keeps it from painting; and CDP input calls (`Input.dispatchMouseEvent`,
|
|
823
|
+
`Input.dispatchKeyEvent`, `Input.insertText`), sent to it in order. Also framework-only, for page
|
|
824
|
+
drivers and agentenv's own steps alike:
|
|
825
|
+
- `navigate {url}` and `history {direction}` (`back`, `forward` or `reload`), in the active tab;
|
|
826
|
+
- `tabs/list`, `tabs/open {url?}`, `tabs/activate {tab}` and `tabs/close {tab}`, by the browser's own tab
|
|
827
|
+
ids. The active tab is the one a dialog is open on, else the one whose page has focus, else the one
|
|
828
|
+
agentenv last brought to the front;
|
|
829
|
+
- `dialog/answer {accept, text?}`, the agent's answer to the dialog open on the active tab;
|
|
830
|
+
- `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
|
|
831
|
+
alone, it intercepts;
|
|
832
|
+
- `page/state`, the active tab's id, URL and title and the dialog or file chooser open on it.
|
|
807
833
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
808
834
|
`~/Downloads`, folders it makes.
|
|
809
835
|
|
|
@@ -32,6 +32,7 @@ from .errors import (
|
|
|
32
32
|
ComputerError,
|
|
33
33
|
DeliveryFailed,
|
|
34
34
|
DeviceUnavailable,
|
|
35
|
+
DialogOpen,
|
|
35
36
|
InputBusy,
|
|
36
37
|
Internal,
|
|
37
38
|
InvalidArgument,
|
|
@@ -47,14 +48,22 @@ from .operations import (
|
|
|
47
48
|
Display,
|
|
48
49
|
Effect,
|
|
49
50
|
ExecResult,
|
|
51
|
+
FileChooser,
|
|
50
52
|
Image,
|
|
53
|
+
PageDialog,
|
|
54
|
+
PageState,
|
|
51
55
|
ReadyCheck,
|
|
56
|
+
Tab,
|
|
52
57
|
capture,
|
|
53
58
|
click,
|
|
59
|
+
dialog,
|
|
54
60
|
display,
|
|
55
61
|
drag,
|
|
62
|
+
history,
|
|
56
63
|
key_down,
|
|
57
64
|
key_up,
|
|
65
|
+
navigate,
|
|
66
|
+
page_state,
|
|
58
67
|
pointer_down,
|
|
59
68
|
pointer_move,
|
|
60
69
|
pointer_position,
|
|
@@ -64,7 +73,9 @@ from .operations import (
|
|
|
64
73
|
run_in_session,
|
|
65
74
|
scroll,
|
|
66
75
|
set_display,
|
|
76
|
+
tabs,
|
|
67
77
|
type_text,
|
|
78
|
+
upload,
|
|
68
79
|
write_files,
|
|
69
80
|
)
|
|
70
81
|
from .dataplane import part_bytes
|
|
@@ -79,6 +90,7 @@ if TYPE_CHECKING:
|
|
|
79
90
|
COMPUTER_DISPLAY,
|
|
80
91
|
COMPUTER_EXEC,
|
|
81
92
|
COMPUTER_KEYBOARD,
|
|
93
|
+
COMPUTER_PAGE,
|
|
82
94
|
COMPUTER_POINTER,
|
|
83
95
|
COMPUTER_READY,
|
|
84
96
|
COMPUTER_RECORDING,
|
|
@@ -91,7 +103,7 @@ if TYPE_CHECKING:
|
|
|
91
103
|
_ON_USE = {
|
|
92
104
|
"driver": ("ComputerDriverEnvironment", "computer_driver", "DEFAULT_IDENTITY", "DriverDeclaration"),
|
|
93
105
|
"card": ("COMPUTER", "COMPUTER_READY", "COMPUTER_DISPLAY", "COMPUTER_SPACES", "COMPUTER_POINTER",
|
|
94
|
-
"COMPUTER_KEYBOARD", "COMPUTER_EXEC", "COMPUTER_RECORDING"),
|
|
106
|
+
"COMPUTER_KEYBOARD", "COMPUTER_PAGE", "COMPUTER_EXEC", "COMPUTER_RECORDING"),
|
|
95
107
|
"recording": ("Recording", "RecordHints", "RecordedFrame", "RecordedSegment", "frames_in_thread"),
|
|
96
108
|
}
|
|
97
109
|
|
|
@@ -110,14 +122,17 @@ __all__ = [
|
|
|
110
122
|
"AppDeclaration", "AppOs",
|
|
111
123
|
"APP_SETTINGS_ENV", "part_bytes",
|
|
112
124
|
"Display", "Image", "Effect", "ExecResult", "ReadyCheck", "Platform", "ComputerSettings", "SETTINGS_ENV",
|
|
125
|
+
"Tab", "PageState", "PageDialog", "FileChooser",
|
|
113
126
|
"PRINTABLE_KEYS",
|
|
114
127
|
"display", "set_display", "capture", "click", "drag", "scroll", "type_text", "press",
|
|
115
128
|
"pointer_move", "pointer_down", "pointer_up", "pointer_position", "key_down", "key_up",
|
|
129
|
+
"navigate", "history", "tabs", "dialog", "upload", "page_state",
|
|
116
130
|
"run_in_session", "write_files", "record", "Recording", "RecordHints", "RecordedFrame", "RecordedSegment",
|
|
117
131
|
"frames_in_thread", "RecordingManifest", "ManifestSegment",
|
|
118
132
|
"ComputerError", "InvalidArgument", "OutOfBounds", "StaleFrame", "InputBusy", "PermissionDenied", "Unsupported",
|
|
119
|
-
"BackgroundUnavailable", "TargetUnavailable", "DeviceUnavailable", "
|
|
133
|
+
"BackgroundUnavailable", "TargetUnavailable", "DeviceUnavailable", "DialogOpen", "LimitExceeded", "Timeout",
|
|
134
|
+
"DeliveryFailed",
|
|
120
135
|
"Internal",
|
|
121
136
|
"COMPUTER", "COMPUTER_READY", "COMPUTER_DISPLAY", "COMPUTER_SPACES", "COMPUTER_POINTER", "COMPUTER_KEYBOARD",
|
|
122
|
-
"COMPUTER_EXEC", "COMPUTER_RECORDING",
|
|
137
|
+
"COMPUTER_PAGE", "COMPUTER_EXEC", "COMPUTER_RECORDING",
|
|
123
138
|
]
|
|
@@ -236,7 +236,7 @@ class AppDeclaration(BaseModel):
|
|
|
236
236
|
"""What a ComputerAppEnvironment class declares, as a ``computer_app`` env stores it: the platforms it ``supports``,
|
|
237
237
|
as a driver names them; ``os``, what it declares for each OS type; ``settings`` and ``build_settings``, the JSON
|
|
238
238
|
schemas of its ``Settings`` and of its ``BuildSettings`` when it declares any; its ``skill``; and the ``data``
|
|
239
|
-
methods, ``install_steps`` and ``ready_checks`` it serves, by name."""
|
|
239
|
+
methods, ``install_steps`` and ``ready_checks`` it serves, by name; and ``browser``, true for a website browser."""
|
|
240
240
|
|
|
241
241
|
model_config = ConfigDict(extra="forbid")
|
|
242
242
|
|
|
@@ -249,6 +249,7 @@ class AppDeclaration(BaseModel):
|
|
|
249
249
|
data: list[str]
|
|
250
250
|
install_steps: list[InstallStep]
|
|
251
251
|
ready_checks: list[AppReadyCheck]
|
|
252
|
+
browser: Optional[bool] = None
|
|
252
253
|
|
|
253
254
|
@model_validator(mode="before")
|
|
254
255
|
@classmethod
|
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import copy
|
|
11
|
+
import json
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Optional
|
|
13
14
|
|
|
@@ -16,7 +17,7 @@ import httpx
|
|
|
16
17
|
from .. import client
|
|
17
18
|
from ..types import RPC_PATH, WELL_KNOWN_PATH, EnvironmentCard
|
|
18
19
|
from .app import COMPUTER_APP, FAILURE, SOCKET
|
|
19
|
-
from .app_runtime import CHECK_TIMEOUT_S, AppReadiness
|
|
20
|
+
from .app_runtime import CHECK_TIMEOUT_S, LAUNCHES, AppReadiness
|
|
20
21
|
from .operations import ReadyCheck
|
|
21
22
|
|
|
22
23
|
#: Where a composed computer installs its apps, a folder each.
|
|
@@ -54,7 +55,7 @@ async def check(folder: Path) -> ReadyCheck:
|
|
|
54
55
|
async def children(folders: list[Path]) -> list[EnvironmentCard]:
|
|
55
56
|
"""Each app in ``folders`` as a child environment of its computer: its server's card, rooted at ``/svc/<name>/``,
|
|
56
57
|
or, while its server isn't answering, its name and route alone."""
|
|
57
|
-
cards = await asyncio.gather(*(
|
|
58
|
+
cards = await asyncio.gather(*(served_card(folder) for folder in folders))
|
|
58
59
|
return [EnvironmentCard.model_validate(child_card(folder.name, card)) if card is not None
|
|
59
60
|
else EnvironmentCard(name=folder.name, url=f"/svc/{folder.name}{RPC_PATH}")
|
|
60
61
|
for folder, card in zip(folders, cards)]
|
|
@@ -85,7 +86,7 @@ def child_card(name: str, card: dict) -> dict:
|
|
|
85
86
|
|
|
86
87
|
async def owned_paths(folders: list[Path]) -> list[str]:
|
|
87
88
|
"""The paths under the session user's home that the apps in ``folders`` keep their data in, as their cards say."""
|
|
88
|
-
cards = await asyncio.gather(*(
|
|
89
|
+
cards = await asyncio.gather(*(served_card(folder) for folder in folders))
|
|
89
90
|
return [path for card in cards if card is not None
|
|
90
91
|
for path in client.extension_params(card, COMPUTER_APP).get("paths") or []]
|
|
91
92
|
|
|
@@ -104,7 +105,7 @@ def endpoint(card: dict, method: str) -> str:
|
|
|
104
105
|
return client.find_extension_method(card, COMPUTER_APP, method)["endpoint"]
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
async def
|
|
108
|
+
async def served_card(folder: Path) -> Optional[dict]:
|
|
108
109
|
"""The card the app in ``folder`` serves, or None while its server isn't answering."""
|
|
109
110
|
try:
|
|
110
111
|
async with _client(folder / SOCKET) as http:
|
|
@@ -113,6 +114,33 @@ async def _card(folder: Path) -> Optional[dict]:
|
|
|
113
114
|
return None
|
|
114
115
|
|
|
115
116
|
|
|
117
|
+
def ended_on_its_own(folder: Path) -> Optional[str]:
|
|
118
|
+
"""Why the app in ``folder`` can't be ready again where nothing launches it but its server: its launch log says it
|
|
119
|
+
ended on its own, an infrastructure failure; None while it hasn't."""
|
|
120
|
+
for entry in launch_log(folder):
|
|
121
|
+
if entry.get("event") == "exit":
|
|
122
|
+
how = (f"by signal {entry['signal']}" if "signal" in entry else
|
|
123
|
+
f"with code {entry['code']}" if "code" in entry else "in a way that wasn't recorded")
|
|
124
|
+
return (f"it ended on its own at {entry.get('at')}, {how}, and nothing launches it again: an "
|
|
125
|
+
"infrastructure failure")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def launch_log(folder: Path) -> list[dict]:
|
|
130
|
+
"""Each entry of the launch log of the app in ``folder`` that's whole, in order."""
|
|
131
|
+
try:
|
|
132
|
+
lines = (folder / LAUNCHES).read_text(encoding="utf-8").splitlines()
|
|
133
|
+
except FileNotFoundError:
|
|
134
|
+
return []
|
|
135
|
+
entries = []
|
|
136
|
+
for line in lines:
|
|
137
|
+
try:
|
|
138
|
+
entries.append(json.loads(line))
|
|
139
|
+
except ValueError:
|
|
140
|
+
continue
|
|
141
|
+
return entries
|
|
142
|
+
|
|
143
|
+
|
|
116
144
|
def _unanswered(folder: Path) -> str:
|
|
117
145
|
try:
|
|
118
146
|
return f"its server failed to start: {(folder / FAILURE).read_text()}"
|
|
@@ -18,6 +18,7 @@ COMPUTER_DISPLAY = "urn:agentenv:computer-display/v1"
|
|
|
18
18
|
COMPUTER_SPACES = "urn:agentenv:computer-spaces/v1"
|
|
19
19
|
COMPUTER_POINTER = "urn:agentenv:computer-pointer/v1"
|
|
20
20
|
COMPUTER_KEYBOARD = "urn:agentenv:computer-keyboard/v1"
|
|
21
|
+
COMPUTER_PAGE = "urn:agentenv:computer-page/v1"
|
|
21
22
|
COMPUTER_EXEC = "urn:agentenv:computer-exec/v1"
|
|
22
23
|
COMPUTER_RECORDING = "urn:agentenv:computer-recording/v1"
|
|
23
24
|
|
|
@@ -29,6 +30,7 @@ GROUP_TOOLS = {
|
|
|
29
30
|
"pointer": ("computer_pointer_move", "computer_pointer_down", "computer_pointer_up", "computer_pointer_position",
|
|
30
31
|
"computer_pointer_list"),
|
|
31
32
|
"keyboard": ("computer_keyboard_down", "computer_keyboard_up"),
|
|
33
|
+
"page": ("computer_navigate", "computer_history", "computer_tabs", "computer_dialog", "computer_upload"),
|
|
32
34
|
}
|
|
33
35
|
#: The tools whose description names the default coordinate space.
|
|
34
36
|
POINT_TOOLS = ("computer_screenshot", "computer_click", "computer_drag", "computer_scroll", "computer_pointer_move",
|
|
@@ -89,7 +91,7 @@ def computer_features(driver: ComputerDriverEnvironment) -> dict:
|
|
|
89
91
|
"max_wait_ms": driver.max_wait_ms, "max_drag_points": driver.max_drag_points,
|
|
90
92
|
"max_duration_ms": driver.max_duration_ms, "max_key_repeat": driver.max_key_repeat,
|
|
91
93
|
"max_scroll_amount": driver.max_scroll_amount, "delivery": "foreground", "platform": driver.platform.as_dict(),
|
|
92
|
-
"supports": list(driver.supports), "default_data_paths": list(DEFAULT_DATA_PATHS),
|
|
94
|
+
"supports": list(driver.supports), "default_data_paths": list(DEFAULT_DATA_PATHS), "screen": driver.screen_kind,
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
|
|
@@ -101,6 +103,12 @@ def declared_extensions(driver: ComputerDriverEnvironment) -> list[EnvironmentEx
|
|
|
101
103
|
declared.append(EnvironmentExtension(
|
|
102
104
|
uri=COMPUTER_POINTER, description="The computer_pointer_* tools, and click and scroll at the pointer.",
|
|
103
105
|
params={"buttons": list(driver.buttons), "kinds": ["foreground"]}))
|
|
106
|
+
if driver.has("page"):
|
|
107
|
+
declared.append(EnvironmentExtension(
|
|
108
|
+
uri=COMPUTER_PAGE, description="The page tools, for a screen that is a website browser's active tab: "
|
|
109
|
+
"computer_navigate, computer_history, computer_tabs, computer_dialog and "
|
|
110
|
+
"computer_upload; each frame names the page it shows.",
|
|
111
|
+
params={"uploads": "~/Uploads"}))
|
|
104
112
|
intake = IntakeDeclaration(get=[IntakeFormat(part="file", format="file-tree")])
|
|
105
113
|
if driver.has("write_files"):
|
|
106
114
|
intake.add = [IntakeFormat(part="file", format="file-tree", load="additive")]
|
|
@@ -37,6 +37,8 @@ def check_features(cls: type) -> None:
|
|
|
37
37
|
problems.append("keys are W3C key values in their W3C case")
|
|
38
38
|
except AgentEnvError as e:
|
|
39
39
|
problems.append(f"keys: {e.message}")
|
|
40
|
+
if cls.screen_kind not in ("desktop", "page"):
|
|
41
|
+
problems.append(f"screen_kind is desktop or page, got {cls.screen_kind!r}")
|
|
40
42
|
if ("set_display" in declared(cls)) != bool(cls.display_modes):
|
|
41
43
|
problems.append("display_modes and @set_display come together")
|
|
42
44
|
problems += check_recording(cls.recording)
|
|
@@ -4,7 +4,7 @@ computer's apps as child environments, each reached at ``/svc/<name>/`` through
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
-
from typing import Any, Callable, ClassVar
|
|
7
|
+
from typing import Any, Callable, ClassVar, Literal
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
from pydantic import BaseModel, ConfigDict
|
|
@@ -79,6 +79,9 @@ class ComputerDriverEnvironment(Tools, Extensions, DataPlane, AgentEnvEnvironmen
|
|
|
79
79
|
recording: ClassVar[Recording | None] = Recording()
|
|
80
80
|
#: What an agent should know about this driver's computer beyond what its card says, as markdown: the end of its skill.
|
|
81
81
|
notes: ClassVar[str] = ""
|
|
82
|
+
#: What the computer's screen shows, its card's ``screen``: a ``desktop``, or a website browser's ``page``, its
|
|
83
|
+
#: active tab.
|
|
84
|
+
screen_kind: ClassVar[Literal["desktop", "page"]] = "desktop"
|
|
82
85
|
tool_access = True
|
|
83
86
|
#: What the base class keeps for the driver, built by ``__init__``.
|
|
84
87
|
_computer: ComputerRuntime | None = None
|
|
@@ -55,6 +55,10 @@ class DeviceUnavailable(ComputerError):
|
|
|
55
55
|
CODE, STATUS, APPLIED, RETRYABLE = "device_unavailable", 503, "no", True
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
class DialogOpen(ComputerError):
|
|
59
|
+
CODE, STATUS, APPLIED = "dialog_open", 409, "no"
|
|
60
|
+
|
|
61
|
+
|
|
58
62
|
class LimitExceeded(ComputerError):
|
|
59
63
|
CODE, STATUS, APPLIED, RETRYABLE = "limit_exceeded", 429, "no", True
|
|
60
64
|
|
|
@@ -45,8 +45,12 @@ class Extensions:
|
|
|
45
45
|
description="Whether the computer is usable now, by real checks; never sends input.")
|
|
46
46
|
async def _computer_ready_status(self) -> dict:
|
|
47
47
|
checks = await self._computer.ready_checks()
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
status = {"ready": all(c.ok for c in checks),
|
|
49
|
+
"checks": [{"name": c.name, "ok": c.ok, "detail": c.detail, **({"final": True} if c.final else {})}
|
|
50
|
+
for c in checks]}
|
|
51
|
+
if failed := [c for c in checks if c.final and not c.ok]:
|
|
52
|
+
status["failed"] = "; ".join(f"{c.name}: {c.detail}" for c in failed)
|
|
53
|
+
return status
|
|
50
54
|
|
|
51
55
|
@extension(COMPUTER_DISPLAY, op="get", method="GET", description="Get or set the display's resolution and scale.")
|
|
52
56
|
async def _computer_display_get(self) -> dict:
|
|
@@ -11,8 +11,8 @@ from datetime import datetime, timezone
|
|
|
11
11
|
from PIL import Image as PILImage
|
|
12
12
|
|
|
13
13
|
from .errors import Internal, StaleFrame
|
|
14
|
-
from .operations import Display, Image
|
|
15
|
-
from .results import DisplayData, FrameData
|
|
14
|
+
from .operations import Display, Image, PageState
|
|
15
|
+
from .results import DialogData, DisplayData, FileChooserData, FrameData, PageData
|
|
16
16
|
|
|
17
17
|
KEEP = 64
|
|
18
18
|
#: The JPEG quality a screenshot answers with when it names none.
|
|
@@ -34,6 +34,8 @@ class Frame:
|
|
|
34
34
|
parent_id: str | None
|
|
35
35
|
captured_at: datetime
|
|
36
36
|
inputs: tuple = field(repr=False)
|
|
37
|
+
#: The page it shows, on a computer whose screen is a website browser's active tab.
|
|
38
|
+
page: PageState | None = None
|
|
37
39
|
|
|
38
40
|
@property
|
|
39
41
|
def width(self) -> int:
|
|
@@ -71,8 +73,21 @@ class Frame:
|
|
|
71
73
|
display=DisplayData(width=self.display.width, height=self.display.height, scale=self.display.scale),
|
|
72
74
|
parent_frame_id=self.parent_id,
|
|
73
75
|
captured_at=self.captured_at.isoformat().replace("+00:00", "Z"),
|
|
76
|
+
page=_page_data(self.page),
|
|
74
77
|
)
|
|
75
78
|
|
|
79
|
+
def note(self) -> str:
|
|
80
|
+
"""What a summary adds for the page it shows: its title and URL, and an open dialog or file chooser."""
|
|
81
|
+
if self.page is None:
|
|
82
|
+
return ""
|
|
83
|
+
page = self.page
|
|
84
|
+
note = f' on "{page.title}" ({page.url})'
|
|
85
|
+
if page.dialog is not None:
|
|
86
|
+
note += f'; a {page.dialog.type} dialog is open: "{page.dialog.message}"'
|
|
87
|
+
if page.file_chooser is not None:
|
|
88
|
+
note += f"; a file chooser is open, for {'files' if page.file_chooser.multiple else 'one file'}"
|
|
89
|
+
return note
|
|
90
|
+
|
|
76
91
|
def encode(self, fmt: str = "png", quality: int | None = None) -> tuple[str, str]:
|
|
77
92
|
"""The frame as base64 and its MIME type."""
|
|
78
93
|
if fmt != "jpeg":
|
|
@@ -92,7 +107,7 @@ class FrameStore:
|
|
|
92
107
|
self._latest: dict[str, str] = {}
|
|
93
108
|
|
|
94
109
|
def add(self, image: PILImage.Image, display: Display, inputs: tuple, *, identity: str,
|
|
95
|
-
parent: Frame | None = None, origin: tuple[int, int] = (0, 0)) -> Frame:
|
|
110
|
+
parent: Frame | None = None, origin: tuple[int, int] = (0, 0), page: PageState | None = None) -> Frame:
|
|
96
111
|
frame = Frame(
|
|
97
112
|
id=f"f{self._next}",
|
|
98
113
|
png=_png(image),
|
|
@@ -103,6 +118,7 @@ class FrameStore:
|
|
|
103
118
|
parent_id=parent.id if parent is not None else None,
|
|
104
119
|
captured_at=parent.captured_at if parent is not None else datetime.now(timezone.utc),
|
|
105
120
|
inputs=parent.inputs if parent is not None else inputs,
|
|
121
|
+
page=parent.page if parent is not None else page,
|
|
106
122
|
)
|
|
107
123
|
self._next += 1
|
|
108
124
|
self._frames[frame.id] = frame
|
|
@@ -153,6 +169,16 @@ def to_pil(capture: object, display: Display) -> PILImage.Image:
|
|
|
153
169
|
return image
|
|
154
170
|
|
|
155
171
|
|
|
172
|
+
def _page_data(page: PageState | None) -> PageData | None:
|
|
173
|
+
if page is None:
|
|
174
|
+
return None
|
|
175
|
+
dialog, chooser = page.dialog, page.file_chooser
|
|
176
|
+
return PageData(tab=page.tab, url=page.url, title=page.title,
|
|
177
|
+
dialog=DialogData(type=dialog.type, message=dialog.message, default_text=dialog.default_text)
|
|
178
|
+
if dialog is not None else None,
|
|
179
|
+
file_chooser=FileChooserData(multiple=chooser.multiple) if chooser is not None else None)
|
|
180
|
+
|
|
181
|
+
|
|
156
182
|
def _png(image: PILImage.Image) -> bytes:
|
|
157
183
|
buffer = io.BytesIO()
|
|
158
184
|
image.save(buffer, format="PNG")
|