* fix: let operators with equal permissions steer each other's turns In shared Control UI sessions, a message sent while another person's turn was running never steered: the Gateway rejected it as a tool-authority mismatch and silently queued it as a follow-up. The steering fingerprint hashed operator identity (profile id, plus the own-browser screen target and own-profile theme target), so two different people could never match even with identical roles. The fingerprint now hashes only permissions. Own-browser screen and theme targets become a profile-free marker; scopes, model policy, access grant (including its identity fallback when unclassified), tool policy, permission mode, caps, and bindings stay exact. Different permissions still queue as a follow-up. Steering never transfers authority: the running turn keeps its owner's authority, browser/screen/theme target, tool bindings, and approval destination. Release note: people with the same permissions can now steer each other's active turns in shared sessions instead of having messages queued. * fix(agents): disambiguate personal tools in steered turns Require an explicit sender-id user selection for screen and theme after accepted cross-profile input. Keep participants with the existing turn authority owner, revalidate their authority at Gateway effects, and release retained authority when the turn ends. Preserve steering admission and single-owner browser bindings. * fix(agents): preserve participants across steering and runtime calls Register participants after accepted inbound input, including automatic-fallback and route-only pending-question acceptance. Carry host participant facts without changing admission. Resolve identity-only personal-tool calls through the exact live run registration, and make participant resolution safe to call unbound. * fix: keep participant input type module-local * fix: expose verified Control UI requester profiles Bind live authenticated requester profiles to complete chat.send contexts without adding SenderId. Ordinary and steered user-role prompts expose requester_profile.id, which screen/theme descriptions and ambiguity guidance identify as the user target. Control UI participants already select the authenticated profile ID; authority, fingerprint, and participant lifecycle remain unchanged. New metadata follows existing per-input context persistence: visible transcript text stays literal, while append-only backends may retain new context without rewriting history or stable prompts. Updated steering and personal-tool documentation. Regression proof: original ingress producer fails the profile and ordinary model-context assertions; the fixed Gateway flow exposes A and B separately, rejects unnamed screen calls listing both profile IDs, and routes the rendered B ID only to B. Final focused files: custody 11/11 (126.94s wall, cross-profile case 2.215s); user-turn 25/25 (41.42s wall); identity 23/23 (38.96s wall); inbound metadata 67/67 (54.18s wall). Core and all 25 selected test type graphs, targeted lint, oxfmt, and diff whitespace checks pass. Tests use existing fixtures without new Gateway boots, timers, or polling. * fix(agents): compare role permissions when admitting steering Prepare the role session access cap, sandbox requirement, and agent allowlist with operator authority. Queue messages with different role permissions as followups while preserving steering for equivalent roles and roles-disabled gateways. Gateway regression: 15 passed; authority unit tests: 38 passed. Restoring all three product files to7eddf5f484makes the three mismatched-role cases fail because their input incorrectly steers. Core and core-test typechecks, targeted formatting/lint, and independent review passed. * test: align steering and heartbeat fixtures with current contracts Accept equal-permission steering from another operator profile in the queued turn regression, retaining rejection for missing authority, changed scopes, and disabled tools. Carry main's fixture fix from6d1826f1ad: wait for database-claim release before creating the replacement heartbeat run. Preserve the existing outcome assertion without adding timers, retries, or production changes. Validation: queued steering 4/4 (114.47s command, 0.894s test execution), heartbeat 40/40 (73.82s command, 69.89s Vitest), and 216 additional steering and authority tests across 11 files. Core typecheck, targeted lint, formatting, and git diff --check pass. CI 36397829641 also reported two inherited Gateway failures. Completion exhaustion remains pending/retryable on both the branch and main7510c4bd52; fixture-order and registry-identity diagnostics did not establish a safe fix. A later diagnostic pass is not evidence of repair. Managed-worktree skill refresh misses the first edited skill event on both heads (expected 2 to be 3). Initial concurrent local runs timed out; isolated runs reproduced the CI assertion unchanged. Leave both unrelated owners unchanged after bounded investigation. Full-candidate independent review against the merge base is scoped-clean at P0/P1, with no accepted or actionable findings. * fix: compare channel role permissions and canonical profile ids in steering Linked-channel operator authority now carries the same prepared role permissions (session access cap, sandbox requirement, agent allowlist) as Control UI ingress through one shared projection, so channel operators with different role limits no longer match. Personal-tool participant selection and its error choices use the canonical profile id that the agent sees as requester_profile.id instead of the transport sender id. * fix(gateway): reject ambiguous personal effects after steering Resolve browser participants at UI dispatch and reject ambiguous Crabbox presentation before reservation. Preserve cancellation if a steer makes presentation ambiguous after reservation. Reject personal instructions and preference reads or writes from mixed-person turns, including at asynchronous effect boundaries. Regression tests fail on 8ffd8dd and pass with this change. Focused suites pass (176 tests); core typecheck, formatting, targeted lint, and full-candidate review pass. Changed test file wall times: environments.session 46.21s, users-personal-file 13.82s, users-preferences 27.92s. * test: type the users.prefs synthetic-call wrapper against typed handlers
5.5 KiB
| summary | title | sidebarTitle | read_when | |||
|---|---|---|---|---|---|---|
| Let an agent arrange the connected Control UI | Screen | Screen |
|
The screen tool lets an agent arrange the browser-based Control UI. It is a
typed layout and navigation surface, not screenshot capture or browser
automation.
The tool is exposed only when the originating client advertises the
ui-commands capability. The selected person's requesting Control UI must still be
connected when the tool runs; otherwise the Gateway returns UNAVAILABLE.
A client advertises ui-commands in the caps array it sends during the
Gateway connect handshake (see
Gateway protocol). The
bundled Control UI advertises it already, so there is nothing to turn on there.
A client that does not advertise it is never offered screen, so the tool is
absent rather than failing at call time.
Actions
| Action | Effect | Optional inputs |
|---|---|---|
split_right |
Split the target session pane to the right | sessionKey (defaults to the current session) |
split_down |
Split the target session pane downward | sessionKey (defaults to the current session) |
close_pane |
Close the target session pane | sessionKey (defaults to the current session) |
focus |
Focus the target session pane | sessionKey (defaults to the current session) |
navigate |
Open the target session | sessionKey (defaults to the current session) |
sidebar_show / sidebar_hide |
Show or hide the main sidebar | - |
terminal_show / terminal_hide |
Show or hide the operator terminal panel | dock (bottom or right) when showing |
browser_show / browser_hide |
Show or hide the browser panel | dock (bottom or right) when showing |
desktop_show / desktop_hide |
Show or hide a remote desktop | environmentId, sessionKey, dock (default right) |
portal_show / portal_hide |
Show or hide a web application portal | portalId, sessionKey, dock (default right) |
Every action accepts optional user, the person's verified requester_profile.id
from the Control UI message's conversation context. When several people have
steered the turn, user is required; the agent chooses the person who asked or
asks them if it is unclear.
For a native application running on an attached environment, use desktop_show
with its environmentId. For a web application, open a portal for the server's
port, then use portal_show with the returned portalId. The selected view opens
in that conversation's side panel. Hiding a view does not stop its application,
close the portal, or release the environment.
The desktop panel and computer tools address the same environment. screen
only presents it; computer tools perform clicks, typing, and screenshots.
An environment can appear before provisioning finishes. Desktop shows startup
progress and connects when that exact machine becomes available. portal_show
can take environmentId while its application is starting; replace it with the
application's portalId when ready. A pending Portal never opens another
application from the portal list.
A successful command returns { "ok": true } after the Gateway sends
the typed ui.command event to the requesting browser.
Routing and security
Commands change only the selected person's requesting Control UI connection. Other
people's dashboards and your other tabs keep their current view. sessionKey
chooses which session to open; it does not choose the recipient.
The Gateway captures the browser target when it accepts the message and keeps
it with queued turns and worker execution. If that browser disconnects or the
turn has no Control UI target, the command fails with UNAVAILABLE. Ask again
from the open Control UI; the command never falls back to a broadcast.
People with matching permissions can steer the same turn. Each participant
keeps their own captured browser target; user can select only the turn's owner
or an accepted participant. A queued or rejected steer does not add a participant.
If the selected person's access has changed, they must ask again.
Standalone RPC and MCP callers that previously used ui.command to broadcast
must invoke it from a requesting Control UI connection or an agent turn started
there. Without that browser target, they now receive UNAVAILABLE, even if
other dashboards are connected. This intentionally replaces the legacy
broadcast contract.
The Gateway RPC requires operator.write. The tool can change presentation
state only: it cannot read pixels, take screenshots, click arbitrary page
content, or bypass the permissions of the selected session and operator
panels.