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.
Files changed (122) hide show
  1. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/PKG-INFO +27 -6
  2. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/README.md +26 -5
  3. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/pyproject.toml +1 -1
  4. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/checks.py +164 -8
  5. agentenv_framework_protocol-0.1.448/src/agentenv_protocol/computer/conformance/page_fixture.py +256 -0
  6. agentenv_framework_protocol-0.1.448/src/agentenv_protocol/computer/seed.py +53 -0
  7. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/website_browser.py +24 -6
  8. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/probe_fake.py +136 -1
  9. agentenv_framework_protocol-0.1.448/tests/test_computer_conformance_page.py +98 -0
  10. agentenv_framework_protocol-0.1.448/tests/test_computer_seed.py +14 -0
  11. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_website_browser.py +17 -4
  12. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/.gitignore +0 -0
  13. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/LICENSE +0 -0
  14. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/NOTICE +0 -0
  15. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/THIRD_PARTY_NOTICES.md +0 -0
  16. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/__init__.py +0 -0
  17. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/advanced_agent.py +0 -0
  18. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/basic_agent.py +0 -0
  19. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/custom_extensions_agent.py +0 -0
  20. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/multimodal_agent.py +0 -0
  21. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/reference_model.py +0 -0
  22. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/examples/streaming_agent.py +0 -0
  23. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/__init__.py +0 -0
  24. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  25. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  26. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  27. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  28. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  29. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  30. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  31. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  32. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  33. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  34. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  35. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/agent_env_environment.py +0 -0
  36. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/client.py +0 -0
  37. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/__init__.py +0 -0
  38. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_declare.py +0 -0
  39. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_linux.py +0 -0
  40. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_load.py +0 -0
  41. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/_with_fds.py +0 -0
  42. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/app.py +0 -0
  43. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/app_data.py +0 -0
  44. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/app_runtime.py +0 -0
  45. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/apps.py +0 -0
  46. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/card.py +0 -0
  47. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/checks.py +0 -0
  48. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  49. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  50. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  51. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
  52. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  53. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  54. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/dataplane.py +0 -0
  55. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/driver.py +0 -0
  56. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/errors.py +0 -0
  57. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/extensions.py +0 -0
  58. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/frames.py +0 -0
  59. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/keys.py +0 -0
  60. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/launch.py +0 -0
  61. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/manifest.py +0 -0
  62. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/operations.py +0 -0
  63. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/platform.py +0 -0
  64. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/queue.py +0 -0
  65. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/recording.py +0 -0
  66. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/results.py +0 -0
  67. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/run_install_step.py +0 -0
  68. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/runtime.py +0 -0
  69. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/serve.py +0 -0
  70. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/serve_app.py +0 -0
  71. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/session.py +0 -0
  72. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/settings.py +0 -0
  73. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/skill.py +0 -0
  74. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/spaces.py +0 -0
  75. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/computer/tools.py +0 -0
  76. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/errors.py +0 -0
  77. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/manifest.py +0 -0
  78. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/preflight.py +0 -0
  79. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/tool_access.py +0 -0
  80. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/tool_client.py +0 -0
  81. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/tool_context.py +0 -0
  82. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/transfers.py +0 -0
  83. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/src/agentenv_protocol/types.py +0 -0
  84. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/computer_fake.py +0 -0
  85. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_agent.py +0 -0
  86. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_agent_examples.py +0 -0
  87. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_cdp_config.py +0 -0
  88. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_object_transfers.py +0 -0
  89. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_peer_agents.py +0 -0
  90. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_proxy.py +0 -0
  91. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_staging.py +0 -0
  92. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_a2a_trajectory_log.py +0 -0
  93. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_agent_env_environment.py +0 -0
  94. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_client.py +0 -0
  95. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_app_data.py +0 -0
  96. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_app_server.py +0 -0
  97. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_conformance.py +0 -0
  98. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_conformance_checks.py +0 -0
  99. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_conformance_recording.py +0 -0
  100. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_dataplane.py +0 -0
  101. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_declare.py +0 -0
  102. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_driver.py +0 -0
  103. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_extensions.py +0 -0
  104. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_frames.py +0 -0
  105. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_page.py +0 -0
  106. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_queue.py +0 -0
  107. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_recording.py +0 -0
  108. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_serve.py +0 -0
  109. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_computer_spaces.py +0 -0
  110. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_control_token.py +0 -0
  111. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_data_objects.py +0 -0
  112. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_errors.py +0 -0
  113. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_extension_methods.py +0 -0
  114. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_intake.py +0 -0
  115. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_manifest.py +0 -0
  116. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_preflight.py +0 -0
  117. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_access.py +0 -0
  118. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_annotations.py +0 -0
  119. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_client.py +0 -0
  120. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_context.py +0 -0
  121. {agentenv_framework_protocol-0.1.445 → agentenv_framework_protocol-0.1.448}/tests/test_tool_context_fastmcp.py +0 -0
  122. {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.445
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. The probe leaves out
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`; `closed_tabs`,
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, else the one
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. The probe leaves out
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`; `closed_tabs`,
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, else the one
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;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.445"
3
+ version = "0.1.448"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -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
- missing = [uri for uri in (COMPUTER_EXEC,) if not self.driver.declares(uri)]
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
- return self._skip_full(f"the fixture is staged with data/add and computer-exec; the driver lacks {missing}")
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
- source = resources.files(__package__).joinpath("fixture.py").read_bytes()
511
- added = await self.driver.add_file(f"{STAGE}/fixture.py", source)
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.py {STAGE}/state > {STAGE}/fixture.log 2>&1 & echo $!")
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.py"))["stdout"].strip()
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
- return None if state["clipboard"] == self.layout["clipboard"] else f"the clipboard is {state['clipboard']!r}"
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