agentenv-framework-protocol 0.1.445__tar.gz → 0.1.448__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.445 → agentenv_framework_protocol-0.1.448}/PKG-INFO +27 -6
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/README.md +26 -5
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/checks.py +164 -8
- agentenv_framework_protocol-0.1.448/src/agentenv_protocol/computer/conformance/page_fixture.py +256 -0
- agentenv_framework_protocol-0.1.448/src/agentenv_protocol/computer/seed.py +53 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/website_browser.py +24 -6
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/probe_fake.py +136 -1
- agentenv_framework_protocol-0.1.448/tests/test_computer_conformance_page.py +98 -0
- agentenv_framework_protocol-0.1.448/tests/test_computer_seed.py +14 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_website_browser.py +17 -4
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/client.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_linux.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/app.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/app_data.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/app_runtime.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/apps.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/card.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/driver.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/errors.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/frames.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/operations.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/platform.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/results.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/runtime.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/session.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/tools.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_client.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_app_server.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_conformance.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_declare.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_driver.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_page.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_recording.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/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.448
|
|
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
|
|
@@ -681,17 +681,36 @@ driver booted with a control token is probed with it: `--token`, by default
|
|
|
681
681
|
| Level | Checks | Needs on the computer |
|
|
682
682
|
|---|---|---|
|
|
683
683
|
| `basic` | P0 ready and a screenshot; P1 the card and tool schemas; P2 native frames, regions and JPEG; P5 refusals and their codes; X the declared extensions (`computer-display`, `computer-spaces`, `computer-keyboard`, `computer-exec`, `data/get`, per-role tool access, the pointer) | nothing |
|
|
684
|
-
| `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held | Linux, `python3` with Tk, a 1280x800 screen or larger, `computer-exec` and `data/add` |
|
|
684
|
+
| `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held; on a page, X the page tools: a prompt answered, a file uploaded, a reload, navigation, back and forward, and tabs | Linux, `python3` with Tk (on a page, `python3` and `computer-page/v1`), a 1280x800 screen or larger, `computer-exec` and `data/add` |
|
|
685
685
|
|
|
686
686
|
At `full` the probe writes the Fixture Desk, a Tk window (`conformance/fixture.py`), into
|
|
687
687
|
`~/.agentenv-probe` with `data/add` and starts it with `computer-exec`. It aims at the boxes the
|
|
688
688
|
window's widgets report, reads back what reached them, and then removes the fixture. When the
|
|
689
|
-
fixture can't be staged, the `full` checks are unverified rather than failed.
|
|
689
|
+
fixture can't be staged, the `full` checks are unverified rather than failed.
|
|
690
|
+
|
|
691
|
+
On a driver whose card says `screen: "page"`, the desk is an HTML page with the same widgets at the
|
|
692
|
+
same places (`conformance/page_fixture.py`). The probe starts its server on loopback, which writes
|
|
693
|
+
what the page reports to the same files, and opens the desk with `computer_tabs` in a tab of its
|
|
694
|
+
own. "Typing leaves the clipboard as it was" fails there when typing arrives as a paste. After the
|
|
695
|
+
input checks it checks the page tools against the desk; a browser that answers dialogs itself makes
|
|
696
|
+
the dialog check not applicable. It then closes its tab and removes the desk, and the file it put in
|
|
697
|
+
`~/Uploads`. The tab that was open before keeps its page and history. The probe leaves out
|
|
690
698
|
the checks for an extension the card doesn't declare. P8 is not applicable to a driver without
|
|
691
699
|
leasing, and unverified for one with it until the probe has lease checks. The probe calls as the
|
|
692
700
|
default role; the tool-access check hides a tool from a role of its own, `agentenv-probe-<random>`,
|
|
693
701
|
and gives it back.
|
|
694
702
|
|
|
703
|
+
### A disk's boot seed
|
|
704
|
+
|
|
705
|
+
A Linux computer's disk form gets the deploy's environment at boot from a NoCloud seed, a FAT filesystem labelled
|
|
706
|
+
`CIDATA` that the launcher writes and the disk's init reads. `agentenv_protocol.computer.seed` defines it. A disk
|
|
707
|
+
composed by one agent-env version boots under another's launcher, so the seed carries every version its launcher
|
|
708
|
+
writes: version 1 in `user-data`, a later version N in `user-data.vN`, and the oldest it carries in
|
|
709
|
+
`agentenv-seed-oldest`. An init reads the newest version it knows, and stops the boot when the seed carries none of
|
|
710
|
+
them. Version 1 is `#agentenv-environment`, then `NAME=<value, base64'd>` a line, in byte order; `SAMPLE` and
|
|
711
|
+
`SAMPLE_USER_DATA` pin it, for a launcher's writer and an init's reader, wherever either is written, to be tested
|
|
712
|
+
against. A change an older init would misread is a new version; one it ignores, such as a new file on the seed, isn't.
|
|
713
|
+
|
|
695
714
|
## Computer apps
|
|
696
715
|
|
|
697
716
|
A computer app is software an agent uses on a computer, beside the driver: a desktop app, a service
|
|
@@ -860,7 +879,8 @@ are the profile's template. The base class adds:
|
|
|
860
879
|
- **Its control connection**, `self.control`, a `BrowserControl` over the pipe. At each launch it attaches
|
|
861
880
|
to every page with flattened sessions, telling each page it attached to as it opened, a popup say, to run, and keeps a
|
|
862
881
|
record, `since` its server started: open tabs in opened order, each with its URL and title as the browser has them
|
|
863
|
-
when the record is read (Chrome sends no event for a title), the one whose page has focus `active`;
|
|
882
|
+
when the record is read (Chrome sends no event for a title), the visible one whose page has focus `active`;
|
|
883
|
+
`closed_tabs`,
|
|
864
884
|
each as of its page's last load, with `closed_at` and
|
|
865
885
|
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
866
886
|
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
@@ -880,8 +900,9 @@ are the profile's template. The base class adds:
|
|
|
880
900
|
drivers and agentenv's own steps alike:
|
|
881
901
|
- `navigate {url}` and `history {direction}` (`back`, `forward` or `reload`), in the active tab;
|
|
882
902
|
- `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
|
|
884
|
-
agentenv last brought to the front
|
|
903
|
+
ids. The active tab is the one a dialog is open on, else the visible one whose page has focus (a hidden
|
|
904
|
+
tab's page that had input still says it has focus), else the one agentenv last brought to the front.
|
|
905
|
+
Headless, a tab opened at a URL opens blank and goes there once it's held to the window's size;
|
|
885
906
|
- `dialog/answer {accept, text?}`, the agent's answer to the dialog open on the active tab;
|
|
886
907
|
- `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
|
|
887
908
|
alone, it intercepts;
|
|
@@ -625,17 +625,36 @@ driver booted with a control token is probed with it: `--token`, by default
|
|
|
625
625
|
| Level | Checks | Needs on the computer |
|
|
626
626
|
|---|---|---|
|
|
627
627
|
| `basic` | P0 ready and a screenshot; P1 the card and tool schemas; P2 native frames, regions and JPEG; P5 refusals and their codes; X the declared extensions (`computer-display`, `computer-spaces`, `computer-keyboard`, `computer-exec`, `data/get`, per-role tool access, the pointer) | nothing |
|
|
628
|
-
| `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held | Linux, `python3` with Tk, a 1280x800 screen or larger, `computer-exec` and `data/add` |
|
|
628
|
+
| `full` | `basic`, then P3 colours land where the mapping says; P4 clicks show their effect; P5 refusals change nothing; P6 typed and pressed keys arrive; P7 scrolls and drags arrive; P9 commands and `data/add` run as one unprivileged user; X pointer and keyboard input, `data/add`, and a recording's segments matching their files with no gap, a repeated `request_id` joining it and `max_concurrent` held; on a page, X the page tools: a prompt answered, a file uploaded, a reload, navigation, back and forward, and tabs | Linux, `python3` with Tk (on a page, `python3` and `computer-page/v1`), a 1280x800 screen or larger, `computer-exec` and `data/add` |
|
|
629
629
|
|
|
630
630
|
At `full` the probe writes the Fixture Desk, a Tk window (`conformance/fixture.py`), into
|
|
631
631
|
`~/.agentenv-probe` with `data/add` and starts it with `computer-exec`. It aims at the boxes the
|
|
632
632
|
window's widgets report, reads back what reached them, and then removes the fixture. When the
|
|
633
|
-
fixture can't be staged, the `full` checks are unverified rather than failed.
|
|
633
|
+
fixture can't be staged, the `full` checks are unverified rather than failed.
|
|
634
|
+
|
|
635
|
+
On a driver whose card says `screen: "page"`, the desk is an HTML page with the same widgets at the
|
|
636
|
+
same places (`conformance/page_fixture.py`). The probe starts its server on loopback, which writes
|
|
637
|
+
what the page reports to the same files, and opens the desk with `computer_tabs` in a tab of its
|
|
638
|
+
own. "Typing leaves the clipboard as it was" fails there when typing arrives as a paste. After the
|
|
639
|
+
input checks it checks the page tools against the desk; a browser that answers dialogs itself makes
|
|
640
|
+
the dialog check not applicable. It then closes its tab and removes the desk, and the file it put in
|
|
641
|
+
`~/Uploads`. The tab that was open before keeps its page and history. The probe leaves out
|
|
634
642
|
the checks for an extension the card doesn't declare. P8 is not applicable to a driver without
|
|
635
643
|
leasing, and unverified for one with it until the probe has lease checks. The probe calls as the
|
|
636
644
|
default role; the tool-access check hides a tool from a role of its own, `agentenv-probe-<random>`,
|
|
637
645
|
and gives it back.
|
|
638
646
|
|
|
647
|
+
### A disk's boot seed
|
|
648
|
+
|
|
649
|
+
A Linux computer's disk form gets the deploy's environment at boot from a NoCloud seed, a FAT filesystem labelled
|
|
650
|
+
`CIDATA` that the launcher writes and the disk's init reads. `agentenv_protocol.computer.seed` defines it. A disk
|
|
651
|
+
composed by one agent-env version boots under another's launcher, so the seed carries every version its launcher
|
|
652
|
+
writes: version 1 in `user-data`, a later version N in `user-data.vN`, and the oldest it carries in
|
|
653
|
+
`agentenv-seed-oldest`. An init reads the newest version it knows, and stops the boot when the seed carries none of
|
|
654
|
+
them. Version 1 is `#agentenv-environment`, then `NAME=<value, base64'd>` a line, in byte order; `SAMPLE` and
|
|
655
|
+
`SAMPLE_USER_DATA` pin it, for a launcher's writer and an init's reader, wherever either is written, to be tested
|
|
656
|
+
against. A change an older init would misread is a new version; one it ignores, such as a new file on the seed, isn't.
|
|
657
|
+
|
|
639
658
|
## Computer apps
|
|
640
659
|
|
|
641
660
|
A computer app is software an agent uses on a computer, beside the driver: a desktop app, a service
|
|
@@ -804,7 +823,8 @@ are the profile's template. The base class adds:
|
|
|
804
823
|
- **Its control connection**, `self.control`, a `BrowserControl` over the pipe. At each launch it attaches
|
|
805
824
|
to every page with flattened sessions, telling each page it attached to as it opened, a popup say, to run, and keeps a
|
|
806
825
|
record, `since` its server started: open tabs in opened order, each with its URL and title as the browser has them
|
|
807
|
-
when the record is read (Chrome sends no event for a title), the one whose page has focus `active`;
|
|
826
|
+
when the record is read (Chrome sends no event for a title), the visible one whose page has focus `active`;
|
|
827
|
+
`closed_tabs`,
|
|
808
828
|
each as of its page's last load, with `closed_at` and
|
|
809
829
|
`closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
|
|
810
830
|
ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
|
|
@@ -824,8 +844,9 @@ are the profile's template. The base class adds:
|
|
|
824
844
|
drivers and agentenv's own steps alike:
|
|
825
845
|
- `navigate {url}` and `history {direction}` (`back`, `forward` or `reload`), in the active tab;
|
|
826
846
|
- `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
|
|
828
|
-
agentenv last brought to the front
|
|
847
|
+
ids. The active tab is the one a dialog is open on, else the visible one whose page has focus (a hidden
|
|
848
|
+
tab's page that had input still says it has focus), else the one agentenv last brought to the front.
|
|
849
|
+
Headless, a tab opened at a URL opens blank and goes there once it's held to the window's size;
|
|
829
850
|
- `dialog/answer {accept, text?}`, the agent's answer to the dialog open on the active tab;
|
|
830
851
|
- `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
|
|
831
852
|
alone, it intercepts;
|
|
@@ -28,6 +28,7 @@ from ..card import (
|
|
|
28
28
|
COMPUTER_DISPLAY,
|
|
29
29
|
COMPUTER_EXEC,
|
|
30
30
|
COMPUTER_KEYBOARD,
|
|
31
|
+
COMPUTER_PAGE,
|
|
31
32
|
COMPUTER_POINTER,
|
|
32
33
|
COMPUTER_READY,
|
|
33
34
|
COMPUTER_RECORDING,
|
|
@@ -39,6 +40,7 @@ from ..manifest import RecordingManifest
|
|
|
39
40
|
from ..recording import RECORDINGS_DIR
|
|
40
41
|
from .client import Driver, image
|
|
41
42
|
from .mp4 import read_mp4
|
|
43
|
+
from .page_fixture import QUESTION, TITLE, TWO, TWO_TITLE
|
|
42
44
|
from .report import CheckResult
|
|
43
45
|
|
|
44
46
|
MINIMUM_TOOLS = ("computer_screenshot", "computer_click", "computer_drag", "computer_scroll", "computer_type",
|
|
@@ -46,6 +48,10 @@ MINIMUM_TOOLS = ("computer_screenshot", "computer_click", "computer_drag", "comp
|
|
|
46
48
|
DATA_PLANE = (INTAKE_EXTENSION_URI, DATA_OBJECTS_EXTENSION_URI)
|
|
47
49
|
#: Where the fixture is staged and keeps its state, relative to the session user's home.
|
|
48
50
|
STAGE = ".agentenv-probe"
|
|
51
|
+
#: Where a page driver's computer_upload takes files from, relative to the session user's home.
|
|
52
|
+
UPLOADS = "Uploads"
|
|
53
|
+
#: What the probe answers the page fixture's prompt with.
|
|
54
|
+
WORD = "tangerine"
|
|
49
55
|
#: What the tool-access check's role, new each run, and the probe's request ids start with.
|
|
50
56
|
PROBE_ROLE = "agentenv-probe"
|
|
51
57
|
#: How long the probe waits for an effect to reach the fixture, and how often it looks.
|
|
@@ -72,6 +78,10 @@ FULL_CHECKS = (("P3", "fixture colours land where the mapping says"), ("P4", "cl
|
|
|
72
78
|
Test = Callable[[], Awaitable[str | None]]
|
|
73
79
|
|
|
74
80
|
|
|
81
|
+
class NotApplicable(Exception):
|
|
82
|
+
"""Raised by a test whose check doesn't apply to this driver, with why."""
|
|
83
|
+
|
|
84
|
+
|
|
75
85
|
class Probe:
|
|
76
86
|
"""One probe run: the driver, an MCP session on its tools, and the results so far. A test returns what went
|
|
77
87
|
wrong, or None when its check passes; a test that raises fails with what it raised."""
|
|
@@ -84,6 +94,12 @@ class Probe:
|
|
|
84
94
|
self.features = driver.params(COMPUTER)
|
|
85
95
|
self.layout: dict = {}
|
|
86
96
|
self.fixture_pid: str | None = None
|
|
97
|
+
self.page = self.features.get("screen") == "page"
|
|
98
|
+
self.fixture = "page_fixture.py" if self.page else "fixture.py"
|
|
99
|
+
self.desk = ""
|
|
100
|
+
self.tab: str | None = None
|
|
101
|
+
self.opened: list[str] = []
|
|
102
|
+
self.upload: str | None = None
|
|
87
103
|
|
|
88
104
|
async def basic(self) -> None:
|
|
89
105
|
for check, section in (("P0", self._ready), ("P1", self._card), ("P2", self._frame), ("P5", self._refusals),
|
|
@@ -95,11 +111,14 @@ class Probe:
|
|
|
95
111
|
self._grants()
|
|
96
112
|
|
|
97
113
|
async def full(self) -> None:
|
|
98
|
-
|
|
114
|
+
needs = (COMPUTER_EXEC, COMPUTER_PAGE) if self.page else (COMPUTER_EXEC,)
|
|
115
|
+
missing = [uri for uri in needs if not self.driver.declares(uri)]
|
|
99
116
|
if not (protocol.intake_declaration(self.driver.card) or {}).get("add"):
|
|
100
117
|
missing.append("data/add")
|
|
101
118
|
if missing:
|
|
102
|
-
|
|
119
|
+
how = "data/add and computer-exec, and opened with computer-page/v1" if self.page else \
|
|
120
|
+
"data/add and computer-exec"
|
|
121
|
+
return self._skip_full(f"the fixture is staged with {how}; the driver lacks {missing}")
|
|
103
122
|
try:
|
|
104
123
|
try:
|
|
105
124
|
problem = await self._stage()
|
|
@@ -109,7 +128,8 @@ class Probe:
|
|
|
109
128
|
return self._skip_full(f"the fixture didn't start: {problem}")
|
|
110
129
|
for check, section in (("P9", self._security), ("P3", self._geometry), ("P4", self._effects),
|
|
111
130
|
("P5", self._unchanged), ("P6", self._typing), ("P7", self._scroll_and_drag),
|
|
112
|
-
("X", self._pointer_input), ("X", self._data_add), ("X", self._recording)
|
|
131
|
+
("X", self._pointer_input), ("X", self._data_add), ("X", self._recording),
|
|
132
|
+
("X", self._page_tools)):
|
|
113
133
|
await self._guarded(check, section)
|
|
114
134
|
finally:
|
|
115
135
|
await self._unstage()
|
|
@@ -119,6 +139,9 @@ class Probe:
|
|
|
119
139
|
async def check(self, check: str, name: str, test: Test, *extensions: str) -> bool:
|
|
120
140
|
try:
|
|
121
141
|
problem = await test()
|
|
142
|
+
except NotApplicable as e:
|
|
143
|
+
self._record(check, name, "not_applicable", str(e), extensions)
|
|
144
|
+
return True
|
|
122
145
|
except Exception as e:
|
|
123
146
|
problem = f"{type(e).__name__}: {e}"
|
|
124
147
|
self._record(check, name, "failed" if problem else "passed", problem or "", extensions)
|
|
@@ -181,6 +204,14 @@ class Probe:
|
|
|
181
204
|
ok, state = await self.until(holds)
|
|
182
205
|
return None if ok else f"the fixture shows {show(state)!r}"
|
|
183
206
|
|
|
207
|
+
async def page_now(self) -> dict:
|
|
208
|
+
"""The page a new frame names, or {} for a frame that names none."""
|
|
209
|
+
return (await self.shot())[0].structuredContent.get("page") or {}
|
|
210
|
+
|
|
211
|
+
async def showing(self, holds: Callable[[dict], bool]) -> str | None:
|
|
212
|
+
ok, page = await self.until(holds, self.page_now)
|
|
213
|
+
return None if ok else f"the frame's page is {page!r}"
|
|
214
|
+
|
|
184
215
|
@staticmethod
|
|
185
216
|
def refused(result: CallToolResult, code: str) -> str | None:
|
|
186
217
|
data = result.structuredContent or {}
|
|
@@ -507,16 +538,20 @@ class Probe:
|
|
|
507
538
|
# full: the fixture
|
|
508
539
|
|
|
509
540
|
async def _stage(self) -> str | None:
|
|
510
|
-
|
|
511
|
-
|
|
541
|
+
"""The Fixture Desk started, or what stopped it: the Tk desk, or on a page, the HTML desk's server, with the
|
|
542
|
+
desk opened in a tab of the probe's own."""
|
|
543
|
+
source = resources.files(__package__).joinpath(self.fixture).read_bytes()
|
|
544
|
+
added = await self.driver.add_file(f"{STAGE}/{self.fixture}", source)
|
|
512
545
|
if "error" in added:
|
|
513
546
|
return f"data/add refused it: {added['error'].get('message')}"
|
|
514
547
|
await self.driver.run("sh", "-c", f"rm -rf {STAGE}/state")
|
|
515
548
|
started = await self.driver.run(
|
|
516
|
-
"sh", "-c", f"python3 {STAGE}/fixture
|
|
549
|
+
"sh", "-c", f"python3 {STAGE}/{self.fixture} {STAGE}/state > {STAGE}/fixture.log 2>&1 & echo $!")
|
|
517
550
|
self.fixture_pid = started["stdout"].strip() or None
|
|
518
551
|
deadline = time.monotonic() + FIXTURE_START_S
|
|
519
552
|
while time.monotonic() < deadline:
|
|
553
|
+
if self.page and self.tab is None and (problem := await self._open_desk()):
|
|
554
|
+
return problem
|
|
520
555
|
layout = await self.driver.run("cat", f"{STAGE}/state/layout.json")
|
|
521
556
|
if layout.get("exit_code") == 0 and layout["stdout"].strip():
|
|
522
557
|
self.layout = json.loads(layout["stdout"])
|
|
@@ -528,10 +563,33 @@ class Probe:
|
|
|
528
563
|
log = await self.driver.run("tail", "-n", "5", f"{STAGE}/fixture.log")
|
|
529
564
|
return f"no layout after {FIXTURE_START_S:g} s: {log['stdout'].strip() or log['stderr'].strip()}"
|
|
530
565
|
|
|
566
|
+
async def _open_desk(self) -> str | None:
|
|
567
|
+
"""Once the page fixture's server has its port, the desk opened in a new tab, which is the probe's."""
|
|
568
|
+
port = await self.driver.run("cat", f"{STAGE}/state/port")
|
|
569
|
+
if port.get("exit_code") != 0 or not port["stdout"].strip():
|
|
570
|
+
return None
|
|
571
|
+
self.desk = f"http://127.0.0.1:{port['stdout'].strip()}/"
|
|
572
|
+
opened = await self.tool("computer_tabs", {"action": "open", "url": self.desk})
|
|
573
|
+
if opened.isError:
|
|
574
|
+
return f"computer_tabs couldn't open it: {opened.content[0].text}"
|
|
575
|
+
self.tab = next((t["tab"] for t in opened.structuredContent["tabs"] if t["active"]), None)
|
|
576
|
+
self.opened += [self.tab] if self.tab else []
|
|
577
|
+
return None if self.tab else f"the tab computer_tabs opened isn't active: {opened.structuredContent['tabs']}"
|
|
578
|
+
|
|
531
579
|
async def _unstage(self) -> None:
|
|
580
|
+
if self.opened:
|
|
581
|
+
with contextlib.suppress(Exception):
|
|
582
|
+
listed = await self.tool("computer_tabs", {"action": "list"})
|
|
583
|
+
left = [t["tab"] for t in listed.structuredContent["tabs"]]
|
|
584
|
+
for tab in [t for t in left if t in self.opened]:
|
|
585
|
+
if len(left) > 1:
|
|
586
|
+
await self.tool("computer_tabs", {"action": "close", "tab": tab})
|
|
587
|
+
left.remove(tab)
|
|
532
588
|
if self.fixture_pid:
|
|
533
589
|
await self.driver.run("kill", self.fixture_pid)
|
|
534
590
|
await self.driver.run("rm", "-rf", STAGE)
|
|
591
|
+
if self.upload:
|
|
592
|
+
await self.driver.run("rm", "-f", f"{UPLOADS}/{self.upload}")
|
|
535
593
|
|
|
536
594
|
async def _base(self) -> tuple[str, float, object]:
|
|
537
595
|
"""A fresh frame of the fixture: its id, the display's scale, and its image."""
|
|
@@ -547,7 +605,7 @@ class Probe:
|
|
|
547
605
|
async def _security(self) -> None:
|
|
548
606
|
async def one_user() -> str | None:
|
|
549
607
|
uid = (await self.driver.run("id", "-u"))["stdout"].strip()
|
|
550
|
-
owner = (await self.driver.run("stat", "-c", "%u", f"{STAGE}/fixture
|
|
608
|
+
owner = (await self.driver.run("stat", "-c", "%u", f"{STAGE}/{self.fixture}"))["stdout"].strip()
|
|
551
609
|
if uid == "0":
|
|
552
610
|
return "commands run as root"
|
|
553
611
|
return None if owner == uid else f"data/add wrote as uid {owner}, commands run as uid {uid}"
|
|
@@ -708,7 +766,11 @@ class Probe:
|
|
|
708
766
|
|
|
709
767
|
async def clipboard() -> str | None:
|
|
710
768
|
state = await self.state()
|
|
711
|
-
|
|
769
|
+
if pasted := state.get("pasted"):
|
|
770
|
+
return f"typing arrived as a paste of {pasted!r}"
|
|
771
|
+
if state["clipboard"] != self.layout["clipboard"]:
|
|
772
|
+
return f"the clipboard is {state['clipboard']!r}"
|
|
773
|
+
return None
|
|
712
774
|
|
|
713
775
|
async def held() -> str | None:
|
|
714
776
|
await clear()
|
|
@@ -789,6 +851,100 @@ class Probe:
|
|
|
789
851
|
|
|
790
852
|
await self.check("X", "a file data/add writes is there for an app to read", picked_up, *DATA_PLANE)
|
|
791
853
|
|
|
854
|
+
async def _page_tools(self) -> None:
|
|
855
|
+
"""The page tools against the HTML desk, each confirmed on the desk or in the page a new frame names; those
|
|
856
|
+
that leave the desk run last."""
|
|
857
|
+
if not self.page:
|
|
858
|
+
return
|
|
859
|
+
frame, scale, _ = await self._base()
|
|
860
|
+
two = self.desk + TWO.lstrip("/")
|
|
861
|
+
|
|
862
|
+
async def dialog() -> str | None:
|
|
863
|
+
before = (await self.state())["saved_count"]
|
|
864
|
+
await self.act("computer_click", {"x": self._at("ask", scale)[0], "y": self._at("ask", scale)[1],
|
|
865
|
+
"frame_id": frame})
|
|
866
|
+
|
|
867
|
+
async def seen() -> tuple[dict, dict]:
|
|
868
|
+
return await self.page_now(), await self.state()
|
|
869
|
+
|
|
870
|
+
ok, (page, state) = await self.until(lambda s: bool(s[0].get("dialog")) or s[1]["prompts"] > 0, seen)
|
|
871
|
+
if not page.get("dialog"):
|
|
872
|
+
if state["prompts"]:
|
|
873
|
+
raise NotApplicable(f"the browser answers dialogs itself: the prompt returned {state['prompted']!r}")
|
|
874
|
+
return f"no dialog shows in the frame's page: {page!r}"
|
|
875
|
+
if page["dialog"] != {"type": "prompt", "message": QUESTION, "default_text": ""}:
|
|
876
|
+
return f"the frame's page shows the dialog as {page['dialog']!r}"
|
|
877
|
+
x, y = self._at("save", scale)
|
|
878
|
+
latest = (await self.shot())[0].structuredContent["frame_id"]
|
|
879
|
+
if problem := self.refused(await self.tool("computer_click", {"x": x, "y": y, "frame_id": latest}),
|
|
880
|
+
"dialog_open"):
|
|
881
|
+
return f"a click while it's open: {problem}"
|
|
882
|
+
await self.act("computer_dialog", {"accept": True, "text": WORD})
|
|
883
|
+
return await self.settled(lambda s: s["prompted"] == WORD and s["saved_count"] == before,
|
|
884
|
+
lambda s: {"prompted": s["prompted"], "saved_count": s["saved_count"]})
|
|
885
|
+
|
|
886
|
+
async def upload() -> str | None:
|
|
887
|
+
self.upload = f"{PROBE_ROLE}-{uuid.uuid4().hex[:8]}.txt"
|
|
888
|
+
text = f"uploaded by the probe {time.time_ns()}"
|
|
889
|
+
added = await self.driver.add_file(f"{UPLOADS}/{self.upload}", text.encode())
|
|
890
|
+
if "error" in added:
|
|
891
|
+
return f"data/add refused it: {added['error'].get('message')}"
|
|
892
|
+
x, y = self._at("file", scale, dx=40)
|
|
893
|
+
await self.act("computer_click", {"x": x, "y": y, "frame_id": frame})
|
|
894
|
+
if problem := await self.showing(lambda p: p.get("file_chooser") is not None):
|
|
895
|
+
return f"no file chooser shows: {problem}"
|
|
896
|
+
outside = await self.tool("computer_upload", {"paths": [f"../{STAGE}/page_fixture.py"]})
|
|
897
|
+
if problem := self.refused(outside, "invalid_argument"):
|
|
898
|
+
return f"a file outside ~/{UPLOADS}: {problem}"
|
|
899
|
+
await self.act("computer_upload", {"paths": [self.upload]})
|
|
900
|
+
return await self.settled(lambda s: s["uploaded"] == {"name": self.upload, "content": text},
|
|
901
|
+
lambda s: s["uploaded"])
|
|
902
|
+
|
|
903
|
+
async def reload() -> str | None:
|
|
904
|
+
loads = (await self.state())["loads"]
|
|
905
|
+
await self.act("computer_history", {"direction": "reload"})
|
|
906
|
+
return await self.settled(lambda s: s["loads"] == loads + 1, lambda s: f"{s['loads'] - loads} loads")
|
|
907
|
+
|
|
908
|
+
async def navigate() -> str | None:
|
|
909
|
+
await self.act("computer_navigate", {"url": two})
|
|
910
|
+
return await self.showing(lambda p: p.get("url") == two and p.get("title") == TWO_TITLE)
|
|
911
|
+
|
|
912
|
+
async def back_and_forth() -> str | None:
|
|
913
|
+
for direction, url, title in (("back", self.desk, TITLE), ("forward", two, TWO_TITLE)):
|
|
914
|
+
await self.act("computer_history", {"direction": direction})
|
|
915
|
+
if problem := await self.showing(lambda p: p.get("url") == url and p.get("title") == title):
|
|
916
|
+
return f"after {direction}: {problem}"
|
|
917
|
+
return None
|
|
918
|
+
|
|
919
|
+
async def tabs() -> str | None:
|
|
920
|
+
opened = await self.tool("computer_tabs", {"action": "open", "url": self.desk})
|
|
921
|
+
if opened.isError:
|
|
922
|
+
return f"open: {opened.content[0].text}"
|
|
923
|
+
active = [t["tab"] for t in opened.structuredContent["tabs"] if t["active"]]
|
|
924
|
+
if len(active) != 1 or active[0] == self.tab:
|
|
925
|
+
return f"the opened tab isn't the one active: {opened.structuredContent['tabs']}"
|
|
926
|
+
other = active[0]
|
|
927
|
+
self.opened.append(other)
|
|
928
|
+
if problem := await self.showing(lambda p: p.get("tab") == other and p.get("url") == self.desk):
|
|
929
|
+
return f"after open: {problem}"
|
|
930
|
+
await self.act("computer_tabs", {"action": "switch", "tab": self.tab})
|
|
931
|
+
if problem := await self.showing(lambda p: p.get("tab") == self.tab):
|
|
932
|
+
return f"after switching back: {problem}"
|
|
933
|
+
closed = await self.tool("computer_tabs", {"action": "close", "tab": other})
|
|
934
|
+
if closed.isError or any(t["tab"] == other for t in closed.structuredContent["tabs"]):
|
|
935
|
+
return f"close: {closed.content[0].text if closed.isError else closed.structuredContent['tabs']}"
|
|
936
|
+
return await self.showing(lambda p: p.get("tab") == self.tab)
|
|
937
|
+
|
|
938
|
+
for name, test in (
|
|
939
|
+
("computer_dialog answers a prompt the frame's page shows, and input waits for it", dialog),
|
|
940
|
+
(f"computer_upload gives the page's file chooser a file from ~/{UPLOADS}, and refuses one outside it",
|
|
941
|
+
upload),
|
|
942
|
+
("computer_history reload loads the page again", reload),
|
|
943
|
+
("computer_navigate opens a page, and the frame's page names it", navigate),
|
|
944
|
+
("computer_history goes back and forward through the tab's pages", back_and_forth),
|
|
945
|
+
("computer_tabs opens, switches to and closes a tab, and the frame shows the active one", tabs)):
|
|
946
|
+
await self.check("X", name, test, COMPUTER_PAGE)
|
|
947
|
+
|
|
792
948
|
async def _recording(self) -> None:
|
|
793
949
|
if not self.driver.declares(COMPUTER_RECORDING):
|
|
794
950
|
return
|