mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(app-server): add remote control client management RPCs (#25785)
## Why
Remote-control clients need to list and revoke controller-device grants
without enabling or enrolling the local relay. These are signed-in
account-management operations, so coupling them to websocket, pairing,
enrollment, or persisted relay state would prevent clients from managing
stale grants from the picker.
Related enhancement request: N/A. This adds the Codex app-server surface
for the planned upstream environment-scoped revoke endpoint.
## What Changed
- Added experimental app-server v2 RPCs:
- `remoteControl/client/list`
- `remoteControl/client/revoke`
- Added picker-oriented protocol types and standard generated schema
fixtures. The list response intentionally omits backend account id,
enrollment status, and location fields.
- Added `app-server-transport/src/transport/remote_control/clients.rs`
for environment-scoped GET and DELETE requests. It builds escaped URL
path segments, forwards optional pagination query fields, sends ChatGPT
auth plus `chatgpt-account-id`, converts RFC3339 `last_seen_at` values
to Unix seconds, accepts `204 No Content` revoke responses, and retries
once after a `401`.
- Extracted shared ChatGPT auth loading and recovery into
`app-server-transport/src/transport/remote_control/auth.rs` so
websocket, pairing, and client management use the same account-auth
boundary.
- Retained the configured remote-control base URL on
`RemoteControlHandle` and resolve management URLs lazily, preserving
deferred validation while relay startup is disabled.
- Registered list as `global_shared_read("remote-control-clients")` and
revoke as `global("remote-control-clients")`.
## Verification
- Added transport coverage proving list and revoke work while relay
state is disabled, IDs are escaped, picker-only fields are returned,
timestamps are converted, revoke accepts `204`, auth headers are
forwarded, `401` retries exactly once, `403` is not retried, and
malformed list payloads retain decode context.
- Added an app-server integration test proving both JSON-RPC methods
work before relay enablement and successful revoke returns `{}`.
- Regenerated and validated experimental and standard app-server schema
fixtures.
This commit is contained in:
@@ -922,6 +922,16 @@ impl MessageProcessor {
|
||||
.pairing_start(params)
|
||||
.await
|
||||
.map(|response| Some(response.into())),
|
||||
ClientRequest::RemoteControlClientsList { params, .. } => self
|
||||
.remote_control_processor
|
||||
.clients_list(params)
|
||||
.await
|
||||
.map(|response| Some(response.into())),
|
||||
ClientRequest::RemoteControlClientsRevoke { params, .. } => self
|
||||
.remote_control_processor
|
||||
.clients_revoke(params)
|
||||
.await
|
||||
.map(|response| Some(response.into())),
|
||||
ClientRequest::ConfigRequirementsRead { params: _, .. } => self
|
||||
.config_processor
|
||||
.config_requirements_read()
|
||||
|
||||
@@ -3,6 +3,10 @@ use crate::error_code::invalid_request;
|
||||
use crate::transport::RemoteControlHandle;
|
||||
use crate::transport::RemoteControlUnavailable;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::RemoteControlClientsListParams;
|
||||
use codex_app_server_protocol::RemoteControlClientsListResponse;
|
||||
use codex_app_server_protocol::RemoteControlClientsRevokeParams;
|
||||
use codex_app_server_protocol::RemoteControlClientsRevokeResponse;
|
||||
use codex_app_server_protocol::RemoteControlDisableResponse;
|
||||
use codex_app_server_protocol::RemoteControlEnableResponse;
|
||||
use codex_app_server_protocol::RemoteControlPairingStartParams;
|
||||
@@ -55,6 +59,26 @@ impl RemoteControlRequestProcessor {
|
||||
.map_err(map_pairing_start_error)
|
||||
}
|
||||
|
||||
pub(crate) async fn clients_list(
|
||||
&self,
|
||||
params: RemoteControlClientsListParams,
|
||||
) -> Result<RemoteControlClientsListResponse, JSONRPCErrorError> {
|
||||
self.handle()?
|
||||
.list_clients(params)
|
||||
.await
|
||||
.map_err(map_client_management_error)
|
||||
}
|
||||
|
||||
pub(crate) async fn clients_revoke(
|
||||
&self,
|
||||
params: RemoteControlClientsRevokeParams,
|
||||
) -> Result<RemoteControlClientsRevokeResponse, JSONRPCErrorError> {
|
||||
self.handle()?
|
||||
.revoke_client(params)
|
||||
.await
|
||||
.map_err(map_client_management_error)
|
||||
}
|
||||
|
||||
fn handle(&self) -> Result<&RemoteControlHandle, JSONRPCErrorError> {
|
||||
self.remote_control_handle
|
||||
.as_ref()
|
||||
@@ -74,5 +98,15 @@ fn map_pairing_start_error(err: io::Error) -> JSONRPCErrorError {
|
||||
}
|
||||
}
|
||||
|
||||
fn map_client_management_error(err: io::Error) -> JSONRPCErrorError {
|
||||
match err.kind() {
|
||||
io::ErrorKind::InvalidInput
|
||||
| io::ErrorKind::NotFound
|
||||
| io::ErrorKind::PermissionDenied
|
||||
| io::ErrorKind::WouldBlock => invalid_request(err.to_string()),
|
||||
_ => internal_error(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod remote_control_processor_tests;
|
||||
|
||||
+31
@@ -46,3 +46,34 @@ fn pairing_start_maps_backend_failures_to_internal_error() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_management_maps_user_actionable_errors_to_invalid_request() {
|
||||
for kind in [
|
||||
io::ErrorKind::InvalidInput,
|
||||
io::ErrorKind::NotFound,
|
||||
io::ErrorKind::PermissionDenied,
|
||||
io::ErrorKind::WouldBlock,
|
||||
] {
|
||||
assert_eq!(
|
||||
map_client_management_error(io::Error::new(kind, "client management unavailable")),
|
||||
JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
data: None,
|
||||
message: "client management unavailable".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_management_maps_backend_failures_to_internal_error() {
|
||||
assert_eq!(
|
||||
map_client_management_error(io::Error::other("client management failed")),
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
data: None,
|
||||
message: "client management failed".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user