feat(app-server): add remote control pairing status RPC (#26450)

## What

Exposes the pairing status transport as experimental app-server v2 RPC
`remoteControl/pairing/status`.

- Adds request/response protocol types for exactly one lookup key:
`pairingCode` or `manualPairingCode`, returning `{ claimed }`.
- Registers the RPC with `global_shared_read("remote-control-pairing")`.
- Wires the method through `MessageProcessor` and
`RemoteControlRequestProcessor`.
- Validates missing/conflicting pairing-code params as invalid requests.
- Documents the RPC in `app-server/README.md`.
- Adds processor, protocol export, and JSON-RPC integration coverage for
both code paths.

## Why

This is the app-server surface the desktop app can poll while the
QR/manual pairing modal is active.

Depends on https://github.com/openai/codex/pull/26449
Related backend change: https://github.com/openai/openai/pull/990244

## Verification

- `cargo test --manifest-path app-server-protocol/Cargo.toml
remote_control`
- `cargo test --manifest-path app-server/Cargo.toml remote_control`
- `cargo fmt --all --check`
- `git diff --check`
This commit is contained in:
hefuc-oai authored and GitHub committed 2026-06-05 10:33:56 -07:00
1 parent 841f057f2d
commit 0177231ca0
8 files changed
+193

No files matched your search

@@ -11,6 +11,8 @@ use codex_app_server_protocol::RemoteControlDisableResponse;
use codex_app_server_protocol::RemoteControlEnableResponse;
use codex_app_server_protocol::RemoteControlPairingStartParams;
use codex_app_server_protocol::RemoteControlPairingStartResponse;
use codex_app_server_protocol::RemoteControlPairingStatusParams;
use codex_app_server_protocol::RemoteControlPairingStatusResponse;
use codex_app_server_protocol::RemoteControlStatusReadResponse;
use std::io;
@@ -60,6 +62,17 @@ impl RemoteControlRequestProcessor {
.map_err(map_pairing_start_error)
}
pub(crate) async fn pairing_status(
&self,
params: RemoteControlPairingStatusParams,
) -> Result<RemoteControlPairingStatusResponse, JSONRPCErrorError> {
validate_pairing_status_params(&params)?;
self.handle()?
.pairing_status(params)
.await
.map_err(map_pairing_start_error)
}
pub(crate) async fn clients_list(
&self,
params: RemoteControlClientsListParams,
@@ -99,6 +112,20 @@ fn map_pairing_start_error(err: io::Error) -> JSONRPCErrorError {
}
}
fn validate_pairing_status_params(
params: &RemoteControlPairingStatusParams,
) -> Result<(), JSONRPCErrorError> {
match (&params.pairing_code, &params.manual_pairing_code) {
(Some(_), None) | (None, Some(_)) => Ok(()),
(Some(_), Some(_)) => Err(invalid_request(
"remoteControl/pairing/status accepts either pairingCode or manualPairingCode, not both",
)),
(None, None) => Err(invalid_request(
"remoteControl/pairing/status requires pairingCode or manualPairingCode",
)),
}
}
fn map_client_management_error(err: io::Error) -> JSONRPCErrorError {
match err.kind() {
io::ErrorKind::InvalidInput
@@ -23,6 +23,59 @@ async fn pairing_start_returns_internal_error_when_remote_control_is_unavailable
);
}
#[tokio::test]
async fn pairing_status_returns_internal_error_when_remote_control_is_unavailable() {
let err = RemoteControlRequestProcessor::new(/*remote_control_handle*/ None)
.pairing_status(RemoteControlPairingStatusParams {
pairing_code: Some("pairing-code".to_string()),
manual_pairing_code: None,
})
.await
.expect_err("missing remote control should fail pairing status");
assert_eq!(
err,
JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
data: None,
message: "remote control is unavailable for this app-server".to_string(),
}
);
}
#[test]
fn pairing_status_rejects_missing_pairing_codes() {
assert_eq!(
validate_pairing_status_params(&RemoteControlPairingStatusParams {
pairing_code: None,
manual_pairing_code: None,
}),
Err(JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
data: None,
message: "remoteControl/pairing/status requires pairingCode or manualPairingCode"
.to_string(),
})
);
}
#[test]
fn pairing_status_rejects_conflicting_pairing_codes() {
assert_eq!(
validate_pairing_status_params(&RemoteControlPairingStatusParams {
pairing_code: Some("pairing-code".to_string()),
manual_pairing_code: Some("ABCD-EFGH".to_string()),
}),
Err(JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
data: None,
message:
"remoteControl/pairing/status accepts either pairingCode or manualPairingCode, not both"
.to_string(),
})
);
}
#[test]
fn pairing_start_maps_invalid_input_to_invalid_request() {
assert_eq!(