agentenv-framework-protocol 0.1.444__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.444 → agentenv_framework_protocol-0.1.445}/PKG-INFO +27 -7
  2. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/README.md +26 -6
  3. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/pyproject.toml +1 -1
  4. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/__init__.py +18 -3
  5. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/card.py +8 -0
  6. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/errors.py +4 -0
  7. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/frames.py +29 -3
  8. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/operations.py +56 -2
  9. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/results.py +31 -0
  10. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/runtime.py +26 -9
  11. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/tools.py +90 -2
  12. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/website_browser.py +224 -11
  13. agentenv_framework_protocol-0.1.445/tests/test_computer_page.py +165 -0
  14. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_website_browser.py +218 -1
  15. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/.gitignore +0 -0
  16. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/LICENSE +0 -0
  17. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/NOTICE +0 -0
  18. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/THIRD_PARTY_NOTICES.md +0 -0
  19. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/__init__.py +0 -0
  20. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/advanced_agent.py +0 -0
  21. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/basic_agent.py +0 -0
  22. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/custom_extensions_agent.py +0 -0
  23. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/multimodal_agent.py +0 -0
  24. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/reference_model.py +0 -0
  25. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/examples/streaming_agent.py +0 -0
  26. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/__init__.py +0 -0
  27. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  28. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  29. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  30. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  31. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  32. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  33. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  34. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  35. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  36. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  37. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  38. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/agent_env_environment.py +0 -0
  39. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/client.py +0 -0
  40. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_declare.py +0 -0
  41. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_linux.py +0 -0
  42. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_load.py +0 -0
  43. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/_with_fds.py +0 -0
  44. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/app.py +0 -0
  45. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/app_data.py +0 -0
  46. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/app_runtime.py +0 -0
  47. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/apps.py +0 -0
  48. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/checks.py +0 -0
  49. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  50. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  51. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
  52. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  53. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
  54. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  55. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  56. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/dataplane.py +0 -0
  57. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/driver.py +0 -0
  58. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/extensions.py +0 -0
  59. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/keys.py +0 -0
  60. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/launch.py +0 -0
  61. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/manifest.py +0 -0
  62. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/platform.py +0 -0
  63. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/queue.py +0 -0
  64. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/recording.py +0 -0
  65. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/run_install_step.py +0 -0
  66. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/serve.py +0 -0
  67. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/serve_app.py +0 -0
  68. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/session.py +0 -0
  69. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/settings.py +0 -0
  70. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/skill.py +0 -0
  71. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/computer/spaces.py +0 -0
  72. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/errors.py +0 -0
  73. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/manifest.py +0 -0
  74. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/preflight.py +0 -0
  75. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/tool_access.py +0 -0
  76. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/tool_client.py +0 -0
  77. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/tool_context.py +0 -0
  78. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/transfers.py +0 -0
  79. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/src/agentenv_protocol/types.py +0 -0
  80. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/computer_fake.py +0 -0
  81. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/probe_fake.py +0 -0
  82. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_agent.py +0 -0
  83. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_agent_examples.py +0 -0
  84. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_cdp_config.py +0 -0
  85. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_object_transfers.py +0 -0
  86. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_peer_agents.py +0 -0
  87. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_proxy.py +0 -0
  88. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_staging.py +0 -0
  89. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_a2a_trajectory_log.py +0 -0
  90. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_agent_env_environment.py +0 -0
  91. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_client.py +0 -0
  92. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_app_data.py +0 -0
  93. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_app_server.py +0 -0
  94. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_conformance.py +0 -0
  95. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_conformance_checks.py +0 -0
  96. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_conformance_recording.py +0 -0
  97. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_dataplane.py +0 -0
  98. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_declare.py +0 -0
  99. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_driver.py +0 -0
  100. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_extensions.py +0 -0
  101. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_frames.py +0 -0
  102. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_queue.py +0 -0
  103. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_recording.py +0 -0
  104. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_serve.py +0 -0
  105. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_computer_spaces.py +0 -0
  106. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_control_token.py +0 -0
  107. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_data_objects.py +0 -0
  108. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_errors.py +0 -0
  109. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_extension_methods.py +0 -0
  110. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_intake.py +0 -0
  111. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_manifest.py +0 -0
  112. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_preflight.py +0 -0
  113. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_tool_access.py +0 -0
  114. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_tool_annotations.py +0 -0
  115. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_tool_client.py +0 -0
  116. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_tool_context.py +0 -0
  117. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_tool_context_fastmcp.py +0 -0
  118. {agentenv_framework_protocol-0.1.444 → agentenv_framework_protocol-0.1.445}/tests/test_tool_context_injection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentenv-framework-protocol
3
- Version: 0.1.444
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,13 +554,21 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
553
554
  group tools. Every result is structured, with a one-line summary as its text block; input results
554
555
  carry `applied`, `delivery` and `others_since_frame`, and `screenshot: true` returns a fresh frame
555
556
  after acting. Click and scroll leave out `x` and `y` to act at the pointer on a computer with
556
- `computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`.
557
+ `computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`. With the page
558
+ group, `computer_navigate` opens a URL in the active tab, `computer_history` goes `back`, `forward`
559
+ or `reload`s, `computer_tabs` lists, opens, switches and closes tabs by the browser's own ids and
560
+ answers with the tabs after it, `computer_dialog` answers the page's open dialog and
561
+ `computer_upload` gives its open file chooser files in `~/Uploads`.
557
562
  - **Frames and spaces.** Points are pixels of a named frame (your latest by default), or a
558
563
  0..`norm_scale` grid over it with `space: "norm"`. A point outside the frame or the range is
559
564
  refused (`out_of_bounds`), never clamped. The last 64 frames are kept, and a frame goes stale
560
- (`stale_frame`) when the display geometry it was captured at changes.
565
+ (`stale_frame`) when the display geometry it was captured at changes. With the page group, every
566
+ frame carries `page`: the active tab's `tab`, `url` and `title`, and the `dialog` (`type`, `message`,
567
+ `default_text`) or `file_chooser` (`multiple`) open on it, which its summary names too.
561
568
  - **The input queue.** Input calls run one at a time in arrival order; one that waits past
562
- `queue_wait_s` is refused `input_busy`. Screenshots and waits never queue.
569
+ `queue_wait_s` is refused `input_busy`. Screenshots and waits never queue. While a dialog is open
570
+ on the page, input it blocks (all but `computer_tabs`, `computer_dialog` and `computer_upload`) is
571
+ refused `dialog_open`, with `applied: "no"`.
563
572
  - **The computer skill.** `computer/v1` declares the seven tools, its params the driver's
564
573
  features, among them `screen`: `desktop`, or `page` for a driver whose screen is a website
565
574
  browser's active tab, which a computer env pairs with exactly one website browser. The driver serves its skill through `skill/v1` (an `@agent_skill` handler on the base
@@ -855,7 +864,8 @@ are the profile's template. The base class adds:
855
864
  each as of its page's last load, with `closed_at` and
856
865
  `closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
857
866
  ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
858
- and, completed, its size and sha256; and `dialogs`, each with its type, message, 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.
859
869
  `hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
860
870
  - **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
861
871
  `record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
@@ -864,8 +874,18 @@ are the profile's template. The base class adds:
864
874
  Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
865
875
  failure, its ready check false. `page/viewport`, `page/capture` and `page/input`, framework-only, are
866
876
  its active tab's page as a page driver sees it: its size, which headless is its window's; a PNG of it,
867
- or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet); and CDP input calls
868
- (`Input.dispatchMouseEvent`, `Input.dispatchKeyEvent`, `Input.insertText`), sent to it in order.
877
+ or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet), its last capture while
878
+ a dialog keeps it from painting; and CDP input calls (`Input.dispatchMouseEvent`,
879
+ `Input.dispatchKeyEvent`, `Input.insertText`), sent to it in order. Also framework-only, for page
880
+ drivers and agentenv's own steps alike:
881
+ - `navigate {url}` and `history {direction}` (`back`, `forward` or `reload`), in the active tab;
882
+ - `tabs/list`, `tabs/open {url?}`, `tabs/activate {tab}` and `tabs/close {tab}`, by the browser's own tab
883
+ ids. The active tab is the one a dialog is open on, else the one whose page has focus, else the one
884
+ agentenv last brought to the front;
885
+ - `dialog/answer {accept, text?}`, the agent's answer to the dialog open on the active tab;
886
+ - `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
887
+ alone, it intercepts;
888
+ - `page/state`, the active tab's id, URL and title and the dialog or file chooser open on it.
869
889
  - **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
870
890
  `~/Downloads`, folders it makes.
871
891
 
@@ -473,6 +473,7 @@ pixels), at the display's size times its scale. Input operations may return an `
473
473
  | `@set_display` | no, with `display_modes` | `computer-display/v1` `set` (`get` is always served) |
474
474
  | `@pointer_move`, `@pointer_down`, `@pointer_up`, `@pointer_position` | all or none | `computer-pointer/v1` and its five tools; click and scroll at the pointer |
475
475
  | `@key_down`, `@key_up` | both or none | `computer-keyboard/v1` and its two tools |
476
+ | `@navigate`, `@history`, `@tabs`, `@dialog`, `@upload`, `@page_state` | all or none, for a screen that is a website browser's page | `computer-page/v1` and its five tools; the `page` of every frame |
476
477
  | `@run_in_session` | no; a Linux default | `computer-exec/v1` |
477
478
  | `@write_files` | no; a Linux default | `data/add` |
478
479
  | `@record` | no | the stream `computer-recording/v1` records from; without it the base class polls `@capture` |
@@ -497,13 +498,21 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
497
498
  group tools. Every result is structured, with a one-line summary as its text block; input results
498
499
  carry `applied`, `delivery` and `others_since_frame`, and `screenshot: true` returns a fresh frame
499
500
  after acting. Click and scroll leave out `x` and `y` to act at the pointer on a computer with
500
- `computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`.
501
+ `computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`. With the page
502
+ group, `computer_navigate` opens a URL in the active tab, `computer_history` goes `back`, `forward`
503
+ or `reload`s, `computer_tabs` lists, opens, switches and closes tabs by the browser's own ids and
504
+ answers with the tabs after it, `computer_dialog` answers the page's open dialog and
505
+ `computer_upload` gives its open file chooser files in `~/Uploads`.
501
506
  - **Frames and spaces.** Points are pixels of a named frame (your latest by default), or a
502
507
  0..`norm_scale` grid over it with `space: "norm"`. A point outside the frame or the range is
503
508
  refused (`out_of_bounds`), never clamped. The last 64 frames are kept, and a frame goes stale
504
- (`stale_frame`) when the display geometry it was captured at changes.
509
+ (`stale_frame`) when the display geometry it was captured at changes. With the page group, every
510
+ frame carries `page`: the active tab's `tab`, `url` and `title`, and the `dialog` (`type`, `message`,
511
+ `default_text`) or `file_chooser` (`multiple`) open on it, which its summary names too.
505
512
  - **The input queue.** Input calls run one at a time in arrival order; one that waits past
506
- `queue_wait_s` is refused `input_busy`. Screenshots and waits never queue.
513
+ `queue_wait_s` is refused `input_busy`. Screenshots and waits never queue. While a dialog is open
514
+ on the page, input it blocks (all but `computer_tabs`, `computer_dialog` and `computer_upload`) is
515
+ refused `dialog_open`, with `applied: "no"`.
507
516
  - **The computer skill.** `computer/v1` declares the seven tools, its params the driver's
508
517
  features, among them `screen`: `desktop`, or `page` for a driver whose screen is a website
509
518
  browser's active tab, which a computer env pairs with exactly one website browser. The driver serves its skill through `skill/v1` (an `@agent_skill` handler on the base
@@ -799,7 +808,8 @@ are the profile's template. The base class adds:
799
808
  each as of its page's last load, with `closed_at` and
800
809
  `closed_by` (`agentenv`, over this connection; `unknown`, anyone else; `exit` or `stop`, when the browser
801
810
  ended on its own or its server stopped it); `downloads`, into `~/Downloads`, each with its state
802
- and, completed, its size and sha256; and `dialogs`, each with its type, message, 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.
803
813
  `hidden_page()` gives agentenv a page of its own for reading state, in no window and never in the record.
804
814
  - **`browser/v1`** on its card: `chromium` (its `version`), `clock: "real"` and, alone, `window`; and
805
815
  `record` (GET), framework-only, which returns that record with `launches`, the app server's log of each
@@ -808,8 +818,18 @@ are the profile's template. The base class adds:
808
818
  Nothing relaunches a browser that exits on its own: in a computer the agent can, and alone it's an infra
809
819
  failure, its ready check false. `page/viewport`, `page/capture` and `page/input`, framework-only, are
810
820
  its active tab's page as a page driver sees it: its size, which headless is its window's; a PNG of it,
811
- or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet); and CDP input calls
812
- (`Input.dispatchMouseEvent`, `Input.dispatchKeyEvent`, `Input.insertText`), sent to it in order.
821
+ or `blank` for a tab that paints nothing within 3 s (1 s with no URL yet), its last capture while
822
+ a dialog keeps it from painting; and CDP input calls (`Input.dispatchMouseEvent`,
823
+ `Input.dispatchKeyEvent`, `Input.insertText`), sent to it in order. Also framework-only, for page
824
+ drivers and agentenv's own steps alike:
825
+ - `navigate {url}` and `history {direction}` (`back`, `forward` or `reload`), in the active tab;
826
+ - `tabs/list`, `tabs/open {url?}`, `tabs/activate {tab}` and `tabs/close {tab}`, by the browser's own tab
827
+ ids. The active tab is the one a dialog is open on, else the one whose page has focus, else the one
828
+ agentenv last brought to the front;
829
+ - `dialog/answer {accept, text?}`, the agent's answer to the dialog open on the active tab;
830
+ - `files/choose {paths}`, files in `~/Uploads` for the file chooser open on it, which headless, and so
831
+ alone, it intercepts;
832
+ - `page/state`, the active tab's id, URL and title and the dialog or file chooser open on it.
813
833
  - **Its skill**: sites by their real names, files to upload in `~/Uploads` and downloads in
814
834
  `~/Downloads`, folders it makes.
815
835
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.444"
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
  ]
@@ -18,6 +18,7 @@ COMPUTER_DISPLAY = "urn:agentenv:computer-display/v1"
18
18
  COMPUTER_SPACES = "urn:agentenv:computer-spaces/v1"
19
19
  COMPUTER_POINTER = "urn:agentenv:computer-pointer/v1"
20
20
  COMPUTER_KEYBOARD = "urn:agentenv:computer-keyboard/v1"
21
+ COMPUTER_PAGE = "urn:agentenv:computer-page/v1"
21
22
  COMPUTER_EXEC = "urn:agentenv:computer-exec/v1"
22
23
  COMPUTER_RECORDING = "urn:agentenv:computer-recording/v1"
23
24
 
@@ -29,6 +30,7 @@ GROUP_TOOLS = {
29
30
  "pointer": ("computer_pointer_move", "computer_pointer_down", "computer_pointer_up", "computer_pointer_position",
30
31
  "computer_pointer_list"),
31
32
  "keyboard": ("computer_keyboard_down", "computer_keyboard_up"),
33
+ "page": ("computer_navigate", "computer_history", "computer_tabs", "computer_dialog", "computer_upload"),
32
34
  }
33
35
  #: The tools whose description names the default coordinate space.
34
36
  POINT_TOOLS = ("computer_screenshot", "computer_click", "computer_drag", "computer_scroll", "computer_pointer_move",
@@ -101,6 +103,12 @@ def declared_extensions(driver: ComputerDriverEnvironment) -> list[EnvironmentEx
101
103
  declared.append(EnvironmentExtension(
102
104
  uri=COMPUTER_POINTER, description="The computer_pointer_* tools, and click and scroll at the pointer.",
103
105
  params={"buttons": list(driver.buttons), "kinds": ["foreground"]}))
106
+ if driver.has("page"):
107
+ declared.append(EnvironmentExtension(
108
+ uri=COMPUTER_PAGE, description="The page tools, for a screen that is a website browser's active tab: "
109
+ "computer_navigate, computer_history, computer_tabs, computer_dialog and "
110
+ "computer_upload; each frame names the page it shows.",
111
+ params={"uploads": "~/Uploads"}))
104
112
  intake = IntakeDeclaration(get=[IntakeFormat(part="file", format="file-tree")])
105
113
  if driver.has("write_files"):
106
114
  intake.add = [IntakeFormat(part="file", format="file-tree", load="additive")]
@@ -55,6 +55,10 @@ class DeviceUnavailable(ComputerError):
55
55
  CODE, STATUS, APPLIED, RETRYABLE = "device_unavailable", 503, "no", True
56
56
 
57
57
 
58
+ class DialogOpen(ComputerError):
59
+ CODE, STATUS, APPLIED = "dialog_open", 409, "no"
60
+
61
+
58
62
  class LimitExceeded(ComputerError):
59
63
  CODE, STATUS, APPLIED, RETRYABLE = "limit_exceeded", 429, "no", True
60
64
 
@@ -11,8 +11,8 @@ from datetime import datetime, timezone
11
11
  from PIL import Image as PILImage
12
12
 
13
13
  from .errors import Internal, StaleFrame
14
- from .operations import Display, Image
15
- from .results import DisplayData, FrameData
14
+ from .operations import Display, Image, PageState
15
+ from .results import DialogData, DisplayData, FileChooserData, FrameData, PageData
16
16
 
17
17
  KEEP = 64
18
18
  #: The JPEG quality a screenshot answers with when it names none.
@@ -34,6 +34,8 @@ class Frame:
34
34
  parent_id: str | None
35
35
  captured_at: datetime
36
36
  inputs: tuple = field(repr=False)
37
+ #: The page it shows, on a computer whose screen is a website browser's active tab.
38
+ page: PageState | None = None
37
39
 
38
40
  @property
39
41
  def width(self) -> int:
@@ -71,8 +73,21 @@ class Frame:
71
73
  display=DisplayData(width=self.display.width, height=self.display.height, scale=self.display.scale),
72
74
  parent_frame_id=self.parent_id,
73
75
  captured_at=self.captured_at.isoformat().replace("+00:00", "Z"),
76
+ page=_page_data(self.page),
74
77
  )
75
78
 
79
+ def note(self) -> str:
80
+ """What a summary adds for the page it shows: its title and URL, and an open dialog or file chooser."""
81
+ if self.page is None:
82
+ return ""
83
+ page = self.page
84
+ note = f' on "{page.title}" ({page.url})'
85
+ if page.dialog is not None:
86
+ note += f'; a {page.dialog.type} dialog is open: "{page.dialog.message}"'
87
+ if page.file_chooser is not None:
88
+ note += f"; a file chooser is open, for {'files' if page.file_chooser.multiple else 'one file'}"
89
+ return note
90
+
76
91
  def encode(self, fmt: str = "png", quality: int | None = None) -> tuple[str, str]:
77
92
  """The frame as base64 and its MIME type."""
78
93
  if fmt != "jpeg":
@@ -92,7 +107,7 @@ class FrameStore:
92
107
  self._latest: dict[str, str] = {}
93
108
 
94
109
  def add(self, image: PILImage.Image, display: Display, inputs: tuple, *, identity: str,
95
- parent: Frame | None = None, origin: tuple[int, int] = (0, 0)) -> Frame:
110
+ parent: Frame | None = None, origin: tuple[int, int] = (0, 0), page: PageState | None = None) -> Frame:
96
111
  frame = Frame(
97
112
  id=f"f{self._next}",
98
113
  png=_png(image),
@@ -103,6 +118,7 @@ class FrameStore:
103
118
  parent_id=parent.id if parent is not None else None,
104
119
  captured_at=parent.captured_at if parent is not None else datetime.now(timezone.utc),
105
120
  inputs=parent.inputs if parent is not None else inputs,
121
+ page=parent.page if parent is not None else page,
106
122
  )
107
123
  self._next += 1
108
124
  self._frames[frame.id] = frame
@@ -153,6 +169,16 @@ def to_pil(capture: object, display: Display) -> PILImage.Image:
153
169
  return image
154
170
 
155
171
 
172
+ def _page_data(page: PageState | None) -> PageData | None:
173
+ if page is None:
174
+ return None
175
+ dialog, chooser = page.dialog, page.file_chooser
176
+ return PageData(tab=page.tab, url=page.url, title=page.title,
177
+ dialog=DialogData(type=dialog.type, message=dialog.message, default_text=dialog.default_text)
178
+ if dialog is not None else None,
179
+ file_chooser=FileChooserData(multiple=chooser.multiple) if chooser is not None else None)
180
+
181
+
156
182
  def _png(image: PILImage.Image) -> bytes:
157
183
  buffer = io.BytesIO()
158
184
  image.save(buffer, format="PNG")
@@ -12,6 +12,16 @@ Each operation is called with screen pixels, positionally, in the order below, a
12
12
  key_down(keys), key_up(keys)
13
13
  run_in_session(argv, cwd, env, timeout_s) -> ExecResult write_files({path: bytes})
14
14
  record(hints) -> an async iterator of RecordedFrame or RecordedSegment, until it is closed
15
+
16
+ The page group, for a driver whose screen is a website browser's active tab:
17
+
18
+ navigate(url) history(direction), back, forward or reload
19
+ tabs(action, tab, url) -> [Tab, ...] list, open, switch or close; the tabs open after it
20
+ dialog(accept, text) upload(paths), relative to the session user's ~/Uploads
21
+ page_state() -> PageState
22
+
23
+ A page driver's ``capture`` may return its ``Image`` with the ``page`` it shows, read with it, which every frame then
24
+ carries rather than one ``page_state`` reads after.
15
25
  """
16
26
  from __future__ import annotations
17
27
 
@@ -25,10 +35,13 @@ REQUIRED = ("display", "capture", "click", "drag", "scroll", "type_text", "press
25
35
  GROUPS = {
26
36
  "pointer": ("pointer_move", "pointer_down", "pointer_up", "pointer_position"),
27
37
  "keyboard": ("key_down", "key_up"),
38
+ "page": ("navigate", "history", "tabs", "dialog", "upload", "page_state"),
28
39
  }
29
40
  OPTIONAL = ("set_display", "run_in_session", "write_files", "record")
30
41
  INPUT = frozenset({"click", "drag", "scroll", "type_text", "press", "pointer_move", "pointer_down", "pointer_up",
31
- "key_down", "key_up"})
42
+ "key_down", "key_up", "navigate", "history", "tabs", "dialog", "upload"})
43
+ #: The input a page's open dialog blocks, which is refused while one is open there.
44
+ DIALOG_BLOCKS = INPUT - {"tabs", "dialog", "upload"}
32
45
 
33
46
  _OP_ATTR = "_aec_operation"
34
47
 
@@ -44,11 +57,12 @@ class Display:
44
57
  @dataclass(frozen=True)
45
58
  class Image:
46
59
  """A capture: encoded ``png`` or ``jpeg`` bytes, or raw pixels in a Pillow raw mode (``RGB``, ``RGBA``, ``BGRA``,
47
- ``BGRX``)."""
60
+ ``BGRX``); and, from a page driver, the page it shows."""
48
61
  width: int
49
62
  height: int
50
63
  data: bytes
51
64
  mode: str = "png"
65
+ page: PageState | None = None
52
66
 
53
67
 
54
68
  @dataclass(frozen=True)
@@ -60,6 +74,40 @@ class Effect:
60
74
  detail: str | None = None
61
75
 
62
76
 
77
+ @dataclass(frozen=True)
78
+ class Tab:
79
+ """A tab of a website browser: its id, the browser's own, and its page's URL and title."""
80
+ id: str
81
+ url: str
82
+ title: str
83
+ active: bool = False
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class PageDialog:
88
+ """A JavaScript dialog open on a page: ``alert``, ``confirm``, ``prompt`` or ``beforeunload``."""
89
+ type: str
90
+ message: str
91
+ default_text: str = ""
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class FileChooser:
96
+ """A file chooser a page opened, waiting for files: several, or one."""
97
+ multiple: bool = False
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class PageState:
102
+ """The page a page driver's screen shows: the active tab's id, URL and title, the dialog open on it, and the file
103
+ chooser it opened that waits."""
104
+ tab: str
105
+ url: str
106
+ title: str
107
+ dialog: PageDialog | None = None
108
+ file_chooser: FileChooser | None = None
109
+
110
+
63
111
  @dataclass(frozen=True)
64
112
  class ExecResult:
65
113
  exit_code: int | None
@@ -115,6 +163,12 @@ pointer_up = _marker("pointer_up")
115
163
  pointer_position = _marker("pointer_position")
116
164
  key_down = _marker("key_down")
117
165
  key_up = _marker("key_up")
166
+ navigate = _marker("navigate")
167
+ history = _marker("history")
168
+ tabs = _marker("tabs")
169
+ dialog = _marker("dialog")
170
+ upload = _marker("upload")
171
+ page_state = _marker("page_state")
118
172
  run_in_session = _marker("run_in_session")
119
173
  write_files = _marker("write_files")
120
174
  record = _marker("record")
@@ -16,6 +16,25 @@ class DisplayData(BaseModel):
16
16
  scale: float
17
17
 
18
18
 
19
+ class DialogData(BaseModel):
20
+ type: str
21
+ message: str
22
+ default_text: str = ""
23
+
24
+
25
+ class FileChooserData(BaseModel):
26
+ multiple: bool
27
+
28
+
29
+ class PageData(BaseModel):
30
+ """The page a frame shows, on a computer whose screen is a website browser's active tab."""
31
+ tab: str
32
+ url: str
33
+ title: str
34
+ dialog: Optional[DialogData] = None
35
+ file_chooser: Optional[FileChooserData] = None
36
+
37
+
19
38
  class FrameData(BaseModel):
20
39
  frame_id: str
21
40
  width: int
@@ -23,6 +42,7 @@ class FrameData(BaseModel):
23
42
  display: DisplayData
24
43
  parent_frame_id: Optional[str] = None
25
44
  captured_at: str
45
+ page: Optional[PageData] = None
26
46
 
27
47
 
28
48
  class ScreenshotResult(FrameData):
@@ -53,6 +73,17 @@ class InputResult(BaseModel):
53
73
  frame: Optional[FrameData] = None
54
74
 
55
75
 
76
+ class TabData(BaseModel):
77
+ tab: str
78
+ url: str
79
+ title: str
80
+ active: bool
81
+
82
+
83
+ class TabsResult(InputResult):
84
+ tabs: list[TabData]
85
+
86
+
56
87
  class WaitResult(BaseModel):
57
88
  summary: str
58
89
  waited_ms: int
@@ -14,9 +14,9 @@ from mcp.types import CallToolResult
14
14
  from ..errors import AgentEnvError
15
15
  from ..tool_context import ToolContext
16
16
  from . import apps
17
- from .errors import DeliveryFailed, DeviceUnavailable, InputBusy, Internal
17
+ from .errors import DeliveryFailed, DeviceUnavailable, DialogOpen, InputBusy, Internal
18
18
  from .frames import Frame, FrameStore, to_pil
19
- from .operations import GROUPS, INPUT, Display, ReadyCheck, resolve
19
+ from .operations import DIALOG_BLOCKS, GROUPS, INPUT, Display, Image, PageState, ReadyCheck, resolve
20
20
  from .queue import InputQueue
21
21
  from .recording import RecordHints, Recordings
22
22
  from .results import EffectData, InputResult, send
@@ -80,8 +80,18 @@ class ComputerRuntime:
80
80
  counts as after the frame, which the frame may not show."""
81
81
  inputs = self.queue.snapshot()
82
82
  display = await self.display_now()
83
- image = to_pil(await self.call("capture"), display)
84
- return self.frames.add(image, display, inputs, identity=identity)
83
+ captured = await self.call("capture")
84
+ image = to_pil(captured, display)
85
+ page = captured.page if isinstance(captured, Image) and isinstance(captured.page, PageState) else None
86
+ if page is None and self.has("page"):
87
+ page = await self.page_now()
88
+ return self.frames.add(image, display, inputs, identity=identity, page=page)
89
+
90
+ async def page_now(self) -> PageState:
91
+ page = await self.call("page_state")
92
+ if not isinstance(page, PageState):
93
+ raise Internal(f"@page_state returned {page!r}, not a PageState")
94
+ return page
85
95
 
86
96
  async def pointer(self) -> tuple[int, int]:
87
97
  where = await self.call("pointer_position")
@@ -93,15 +103,20 @@ class ComputerRuntime:
93
103
 
94
104
  async def act(self, ctx: ToolContext, operation: str, args: tuple, *, frame: Frame | None, screenshot: bool,
95
105
  summary: str, mapped: bool = False, at_pointer: bool = False, repeat: int = 1,
96
- hold: tuple | None = None, move_to: tuple | None = None, then: Callable | None = None) -> CallToolResult:
106
+ hold: tuple | None = None, move_to: tuple | None = None, then: Callable | None = None,
107
+ wrap: Callable[[InputResult, Any], InputResult] | None = None) -> CallToolResult:
97
108
  """One input call: its turn in the queue, the operation (after a move, repeated or held), then the result
98
109
  with others' input since ``frame`` and, when asked, a fresh frame. Whatever the call's turn can change is read
99
110
  once it has the turn: a point ``mapped`` from ``frame`` is checked again, since the display may have changed
100
- while it waited, and ``at_pointer`` reads the pointer and puts it before ``args``. ``then`` runs as soon as
101
- the input is delivered."""
111
+ while it waited, and ``at_pointer`` reads the pointer and puts it before ``args``; input a page's open dialog
112
+ blocks is refused. ``then`` runs as soon as the input is delivered, and ``wrap`` makes the result from the
113
+ operation's own."""
102
114
  identity = self.driver.caller_identity(ctx)
103
115
  sent = False
104
116
  async with self.queue.turn(self.driver.settings.queue_wait_s):
117
+ if operation in DIALOG_BLOCKS and self.has("page") and (dialog := (await self.page_now()).dialog):
118
+ raise DialogOpen(f'a {dialog.type} dialog is open on the page: "{dialog.message}"; answer it with '
119
+ "computer_dialog first")
105
120
  if mapped and frame is not None:
106
121
  self.frames.check_current(frame, await self.display_now())
107
122
  if at_pointer:
@@ -129,14 +144,16 @@ class ComputerRuntime:
129
144
  then()
130
145
  result = InputResult(summary=summary, applied="yes", delivery="foreground", others_since_frame=others,
131
146
  effect=EffectData.of(effect))
147
+ if wrap is not None:
148
+ result = wrap(result, effect)
132
149
  if not screenshot:
133
150
  return send(result)
134
151
  try:
135
152
  new = await self.capture(identity)
136
153
  except AgentEnvError as e:
137
- result.summary = f"{summary}; the screenshot after it failed ({e.code}: {e.message})"
154
+ result.summary = f"{result.summary}; the screenshot after it failed ({e.code}: {e.message})"
138
155
  return send(result)
139
- result.summary, result.frame = f"{summary}, then captured {new.id}", new.result()
156
+ result.summary, result.frame = f"{result.summary}, then captured {new.id}{new.note()}", new.result()
140
157
  return send(result, new.encode())
141
158
 
142
159
  async def _hold_for(self, ms: int, keys: list[str]) -> None: