feat(remote-control): add pairing start (#25675)

## Why

Remote control enrollment authorizes a desktop server, but app-server v2
did not expose the follow-up pairing operation needed to mint a
short-lived controller pairing artifact from that enrolled server.
Clients need a narrow RPC that starts pairing without exposing the
backend `serverId` or conflating pairing with websocket connection
state.

Issue: N/A; internal remote-control pairing API change.

## What Changed

Added experimental app-server v2 `remoteControl/pairing/start` with
`manualCode` input and `pairingCode`, nullable `manualPairingCode`,
`environmentId`, and Unix-seconds `expiresAt` output. The method
serializes under its own `global("remote-control-pairing")` scope and is
documented in `app-server/README.md`.

Extended the remote-control transport with private `/server/pair`
request/response types and normalized `pair_url` handling. Pairing uses
the current enrolled server bearer, refreshes that bearer when needed,
keeps backend `server_id` private, validates returned `server_id` and
`environment_id` against the current enrollment, and preserves backend
status/header/body context for failures and malformed responses.

Wired the request through `RemoteControlRequestProcessor` and
`MessageProcessor`, mapping unavailable/disabled pairing to
`invalid_request` and backend failures to internal errors.

## Verification

- `just test -p codex-app-server-transport`
- `just test -p codex-app-server
remote_control_pairing_start_returns_pairing_artifacts`
This commit is contained in:
Anton Panasenko authored and GitHub committed 2026-06-02 01:05:50 +00:00
1 parent 1ad0d7aa4b
commit 0002316687
15 files changed
+1302 -48

No files matched your search

@@ -843,6 +843,12 @@ client_request_definitions! {
serialization: global_shared_read("remote-control"),
response: v2::RemoteControlStatusReadResponse,
},
#[experimental("remoteControl/pairing/start")]
RemoteControlPairingStart => "remoteControl/pairing/start" {
params: v2::RemoteControlPairingStartParams,
serialization: global("remote-control-pairing"),
response: v2::RemoteControlPairingStartResponse,
},
#[experimental("collaborationMode/list")]
/// Lists collaboration mode presets.
CollaborationModeList => "collaborationMode/list" {
@@ -1977,6 +1983,17 @@ mod tests {
},
};
assert_eq!(mcp_resource_read.serialization_scope(), None);
let remote_control_pairing_start = ClientRequest::RemoteControlPairingStart {
request_id: request_id(),
params: v2::RemoteControlPairingStartParams::default(),
};
assert_eq!(
remote_control_pairing_start.serialization_scope(),
Some(ClientRequestSerializationScope::Global(
"remote-control-pairing"
))
);
}
#[test]