agentenv-framework-protocol 0.1.444__tar.gz → 0.1.446__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.444 → agentenv_framework_protocol-0.1.446}/PKG-INFO +27 -7
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/README.md +26 -6
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/__init__.py +18 -3
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/card.py +8 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/errors.py +4 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/frames.py +29 -3
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/operations.py +56 -2
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/results.py +31 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/runtime.py +26 -9
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/tools.py +90 -2
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/website_browser.py +224 -11
- agentenv_framework_protocol-0.1.446/tests/test_computer_page.py +165 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_website_browser.py +218 -1
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/client.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/app.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/app_data.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/app_runtime.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/apps.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/driver.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/platform.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/session.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/probe_fake.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_client.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_app_server.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_driver.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.446}/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.446
|
|
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,13 +554,21 @@ 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
573
|
features, among them `screen`: `desktop`, or `page` for a driver whose screen is a website
|
|
565
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
|
|
@@ -855,7 +864,8 @@ are the profile's template. The base class adds:
|
|
|
855
864
|
each as of its page's last load, with `closed_at` and
|
|
856
865
|
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
857
866
|
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
858
|
-
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.
|
|
859
869
|
`hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
|
|
860
870
|
- **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
|
|
861
871
|
`record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
|
|
@@ -864,8 +874,18 @@ are the profile's template. The base class adds:
|
|
|
864
874
|
Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
|
|
865
875
|
failure, its ready check false. `page/viewport`, `page/capture` and `page/input`, framework-only, are
|
|
866
876
|
its active tab's page as a page driver sees it: its size, which headless is its window's; a PNG of it,
|
|
867
|
-
or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet)
|
|
868
|
-
|
|
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.
|
|
869
889
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
870
890
|
`~/Downloads`, folders it makes.
|
|
871
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,13 +498,21 @@ 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
517
|
features, among them `screen`: `desktop`, or `page` for a driver whose screen is a website
|
|
509
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
|
|
@@ -799,7 +808,8 @@ are the profile's template. The base class adds:
|
|
|
799
808
|
each as of its page's last load, with `closed_at` and
|
|
800
809
|
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
801
810
|
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
802
|
-
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.
|
|
803
813
|
`hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
|
|
804
814
|
- **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
|
|
805
815
|
`record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
|
|
@@ -808,8 +818,18 @@ are the profile's template. The base class adds:
|
|
|
808
818
|
Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
|
|
809
819
|
failure, its ready check false. `page/viewport`, `page/capture` and `page/input`, framework-only, are
|
|
810
820
|
its active tab's page as a page driver sees it: its size, which headless is its window's; a PNG of it,
|
|
811
|
-
or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet)
|
|
812
|
-
|
|
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.
|
|
813
833
|
- **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
|
|
814
834
|
`~/Downloads`, folders it makes.
|
|
815
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
|
]
|
|
@@ -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",
|
|
@@ -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")]
|
|
@@ -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
|
|
|
@@ -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")
|
|
@@ -12,6 +12,16 @@ Each operation is called with screen pixels, positionally, in the order below, a
|
|
|
12
12
|
key_down(keys), key_up(keys)
|
|
13
13
|
run_in_session(argv, cwd, env, timeout_s) -> ExecResult write_files({path: bytes})
|
|
14
14
|
record(hints) -> an async iterator of RecordedFrame or RecordedSegment, until it is closed
|
|
15
|
+
|
|
16
|
+
The page group, for a driver whose screen is a website browser's active tab:
|
|
17
|
+
|
|
18
|
+
navigate(url) history(direction), back, forward or reload
|
|
19
|
+
tabs(action, tab, url) -> [Tab, ...] list, open, switch or close; the tabs open after it
|
|
20
|
+
dialog(accept, text) upload(paths), relative to the session user's ~/Uploads
|
|
21
|
+
page_state() -> PageState
|
|
22
|
+
|
|
23
|
+
A page driver's ``capture`` may return its ``Image`` with the ``page`` it shows, read with it, which every frame then
|
|
24
|
+
carries rather than one ``page_state`` reads after.
|
|
15
25
|
"""
|
|
16
26
|
from __future__ import annotations
|
|
17
27
|
|
|
@@ -25,10 +35,13 @@ REQUIRED = ("display", "capture", "click", "drag", "scroll", "type_text", "press
|
|
|
25
35
|
GROUPS = {
|
|
26
36
|
"pointer": ("pointer_move", "pointer_down", "pointer_up", "pointer_position"),
|
|
27
37
|
"keyboard": ("key_down", "key_up"),
|
|
38
|
+
"page": ("navigate", "history", "tabs", "dialog", "upload", "page_state"),
|
|
28
39
|
}
|
|
29
40
|
OPTIONAL = ("set_display", "run_in_session", "write_files", "record")
|
|
30
41
|
INPUT = frozenset({"click", "drag", "scroll", "type_text", "press", "pointer_move", "pointer_down", "pointer_up",
|
|
31
|
-
"key_down", "key_up"})
|
|
42
|
+
"key_down", "key_up", "navigate", "history", "tabs", "dialog", "upload"})
|
|
43
|
+
#: The input a page's open dialog blocks, which is refused while one is open there.
|
|
44
|
+
DIALOG_BLOCKS = INPUT - {"tabs", "dialog", "upload"}
|
|
32
45
|
|
|
33
46
|
_OP_ATTR = "_aec_operation"
|
|
34
47
|
|
|
@@ -44,11 +57,12 @@ class Display:
|
|
|
44
57
|
@dataclass(frozen=True)
|
|
45
58
|
class Image:
|
|
46
59
|
"""A capture: encoded ``png`` or ``jpeg`` bytes, or raw pixels in a Pillow raw mode (``RGB``, ``RGBA``, ``BGRA``,
|
|
47
|
-
``BGRX``)."""
|
|
60
|
+
``BGRX``); and, from a page driver, the page it shows."""
|
|
48
61
|
width: int
|
|
49
62
|
height: int
|
|
50
63
|
data: bytes
|
|
51
64
|
mode: str = "png"
|
|
65
|
+
page: PageState | None = None
|
|
52
66
|
|
|
53
67
|
|
|
54
68
|
@dataclass(frozen=True)
|
|
@@ -60,6 +74,40 @@ class Effect:
|
|
|
60
74
|
detail: str | None = None
|
|
61
75
|
|
|
62
76
|
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class Tab:
|
|
79
|
+
"""A tab of a website browser: its id, the browser's own, and its page's URL and title."""
|
|
80
|
+
id: str
|
|
81
|
+
url: str
|
|
82
|
+
title: str
|
|
83
|
+
active: bool = False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class PageDialog:
|
|
88
|
+
"""A JavaScript dialog open on a page: ``alert``, ``confirm``, ``prompt`` or ``beforeunload``."""
|
|
89
|
+
type: str
|
|
90
|
+
message: str
|
|
91
|
+
default_text: str = ""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class FileChooser:
|
|
96
|
+
"""A file chooser a page opened, waiting for files: several, or one."""
|
|
97
|
+
multiple: bool = False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class PageState:
|
|
102
|
+
"""The page a page driver's screen shows: the active tab's id, URL and title, the dialog open on it, and the file
|
|
103
|
+
chooser it opened that waits."""
|
|
104
|
+
tab: str
|
|
105
|
+
url: str
|
|
106
|
+
title: str
|
|
107
|
+
dialog: PageDialog | None = None
|
|
108
|
+
file_chooser: FileChooser | None = None
|
|
109
|
+
|
|
110
|
+
|
|
63
111
|
@dataclass(frozen=True)
|
|
64
112
|
class ExecResult:
|
|
65
113
|
exit_code: int | None
|
|
@@ -115,6 +163,12 @@ pointer_up = _marker("pointer_up")
|
|
|
115
163
|
pointer_position = _marker("pointer_position")
|
|
116
164
|
key_down = _marker("key_down")
|
|
117
165
|
key_up = _marker("key_up")
|
|
166
|
+
navigate = _marker("navigate")
|
|
167
|
+
history = _marker("history")
|
|
168
|
+
tabs = _marker("tabs")
|
|
169
|
+
dialog = _marker("dialog")
|
|
170
|
+
upload = _marker("upload")
|
|
171
|
+
page_state = _marker("page_state")
|
|
118
172
|
run_in_session = _marker("run_in_session")
|
|
119
173
|
write_files = _marker("write_files")
|
|
120
174
|
record = _marker("record")
|
|
@@ -16,6 +16,25 @@ class DisplayData(BaseModel):
|
|
|
16
16
|
scale: float
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
class DialogData(BaseModel):
|
|
20
|
+
type: str
|
|
21
|
+
message: str
|
|
22
|
+
default_text: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FileChooserData(BaseModel):
|
|
26
|
+
multiple: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PageData(BaseModel):
|
|
30
|
+
"""The page a frame shows, on a computer whose screen is a website browser's active tab."""
|
|
31
|
+
tab: str
|
|
32
|
+
url: str
|
|
33
|
+
title: str
|
|
34
|
+
dialog: Optional[DialogData] = None
|
|
35
|
+
file_chooser: Optional[FileChooserData] = None
|
|
36
|
+
|
|
37
|
+
|
|
19
38
|
class FrameData(BaseModel):
|
|
20
39
|
frame_id: str
|
|
21
40
|
width: int
|
|
@@ -23,6 +42,7 @@ class FrameData(BaseModel):
|
|
|
23
42
|
display: DisplayData
|
|
24
43
|
parent_frame_id: Optional[str] = None
|
|
25
44
|
captured_at: str
|
|
45
|
+
page: Optional[PageData] = None
|
|
26
46
|
|
|
27
47
|
|
|
28
48
|
class ScreenshotResult(FrameData):
|
|
@@ -53,6 +73,17 @@ class InputResult(BaseModel):
|
|
|
53
73
|
frame: Optional[FrameData] = None
|
|
54
74
|
|
|
55
75
|
|
|
76
|
+
class TabData(BaseModel):
|
|
77
|
+
tab: str
|
|
78
|
+
url: str
|
|
79
|
+
title: str
|
|
80
|
+
active: bool
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TabsResult(InputResult):
|
|
84
|
+
tabs: list[TabData]
|
|
85
|
+
|
|
86
|
+
|
|
56
87
|
class WaitResult(BaseModel):
|
|
57
88
|
summary: str
|
|
58
89
|
waited_ms: int
|
|
@@ -14,9 +14,9 @@ from mcp.types import CallToolResult
|
|
|
14
14
|
from ..errors import AgentEnvError
|
|
15
15
|
from ..tool_context import ToolContext
|
|
16
16
|
from . import apps
|
|
17
|
-
from .errors import DeliveryFailed, DeviceUnavailable, InputBusy, Internal
|
|
17
|
+
from .errors import DeliveryFailed, DeviceUnavailable, DialogOpen, InputBusy, Internal
|
|
18
18
|
from .frames import Frame, FrameStore, to_pil
|
|
19
|
-
from .operations import GROUPS, INPUT, Display, ReadyCheck, resolve
|
|
19
|
+
from .operations import DIALOG_BLOCKS, GROUPS, INPUT, Display, Image, PageState, ReadyCheck, resolve
|
|
20
20
|
from .queue import InputQueue
|
|
21
21
|
from .recording import RecordHints, Recordings
|
|
22
22
|
from .results import EffectData, InputResult, send
|
|
@@ -80,8 +80,18 @@ class ComputerRuntime:
|
|
|
80
80
|
counts as after the frame, which the frame may not show."""
|
|
81
81
|
inputs = self.queue.snapshot()
|
|
82
82
|
display = await self.display_now()
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
captured = await self.call("capture")
|
|
84
|
+
image = to_pil(captured, display)
|
|
85
|
+
page = captured.page if isinstance(captured, Image) and isinstance(captured.page, PageState) else None
|
|
86
|
+
if page is None and self.has("page"):
|
|
87
|
+
page = await self.page_now()
|
|
88
|
+
return self.frames.add(image, display, inputs, identity=identity, page=page)
|
|
89
|
+
|
|
90
|
+
async def page_now(self) -> PageState:
|
|
91
|
+
page = await self.call("page_state")
|
|
92
|
+
if not isinstance(page, PageState):
|
|
93
|
+
raise Internal(f"@page_state returned {page!r}, not a PageState")
|
|
94
|
+
return page
|
|
85
95
|
|
|
86
96
|
async def pointer(self) -> tuple[int, int]:
|
|
87
97
|
where = await self.call("pointer_position")
|
|
@@ -93,15 +103,20 @@ class ComputerRuntime:
|
|
|
93
103
|
|
|
94
104
|
async def act(self, ctx: ToolContext, operation: str, args: tuple, *, frame: Frame | None, screenshot: bool,
|
|
95
105
|
summary: str, mapped: bool = False, at_pointer: bool = False, repeat: int = 1,
|
|
96
|
-
hold: tuple | None = None, move_to: tuple | None = None, then: Callable | None = None
|
|
106
|
+
hold: tuple | None = None, move_to: tuple | None = None, then: Callable | None = None,
|
|
107
|
+
wrap: Callable[[InputResult, Any], InputResult] | None = None) -> CallToolResult:
|
|
97
108
|
"""One input call: its turn in the queue, the operation (after a move, repeated or held), then the result
|
|
98
109
|
with others' input since ``frame`` and, when asked, a fresh frame. Whatever the call's turn can change is read
|
|
99
110
|
once it has the turn: a point ``mapped`` from ``frame`` is checked again, since the display may have changed
|
|
100
|
-
while it waited, and ``at_pointer`` reads the pointer and puts it before ``args
|
|
101
|
-
the input is delivered
|
|
111
|
+
while it waited, and ``at_pointer`` reads the pointer and puts it before ``args``; input a page's open dialog
|
|
112
|
+
blocks is refused. ``then`` runs as soon as the input is delivered, and ``wrap`` makes the result from the
|
|
113
|
+
operation's own."""
|
|
102
114
|
identity = self.driver.caller_identity(ctx)
|
|
103
115
|
sent = False
|
|
104
116
|
async with self.queue.turn(self.driver.settings.queue_wait_s):
|
|
117
|
+
if operation in DIALOG_BLOCKS and self.has("page") and (dialog := (await self.page_now()).dialog):
|
|
118
|
+
raise DialogOpen(f'a {dialog.type} dialog is open on the page: "{dialog.message}"; answer it with '
|
|
119
|
+
"computer_dialog first")
|
|
105
120
|
if mapped and frame is not None:
|
|
106
121
|
self.frames.check_current(frame, await self.display_now())
|
|
107
122
|
if at_pointer:
|
|
@@ -129,14 +144,16 @@ class ComputerRuntime:
|
|
|
129
144
|
then()
|
|
130
145
|
result = InputResult(summary=summary, applied="yes", delivery="foreground", others_since_frame=others,
|
|
131
146
|
effect=EffectData.of(effect))
|
|
147
|
+
if wrap is not None:
|
|
148
|
+
result = wrap(result, effect)
|
|
132
149
|
if not screenshot:
|
|
133
150
|
return send(result)
|
|
134
151
|
try:
|
|
135
152
|
new = await self.capture(identity)
|
|
136
153
|
except AgentEnvError as e:
|
|
137
|
-
result.summary = f"{summary}; the screenshot after it failed ({e.code}: {e.message})"
|
|
154
|
+
result.summary = f"{result.summary}; the screenshot after it failed ({e.code}: {e.message})"
|
|
138
155
|
return send(result)
|
|
139
|
-
result.summary, result.frame = f"{summary}, then captured {new.id}", new.result()
|
|
156
|
+
result.summary, result.frame = f"{result.summary}, then captured {new.id}{new.note()}", new.result()
|
|
140
157
|
return send(result, new.encode())
|
|
141
158
|
|
|
142
159
|
async def _hold_for(self, ms: int, keys: list[str]) -> None:
|