agentenv-framework-protocol 0.1.443__tar.gz → 0.1.445__tar.gz

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