mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(app-server): persist remote-control desired state (#27445)
## Why
Remote-control runtime enablement and persisted enrollment preference
were represented by separate flags. That made startup rehydration, RPC
persistence, and new-enrollment seeding race with one another, and it
did not cleanly distinguish runtime-only CLI or daemon starts from
durable app-server RPC changes.
## What Changed
- Replace the parallel enablement, seed, and rehydration flags with one
transport-owned `RemoteControlDesiredState`.
- Add nullable enrollment-scoped persistence and preserve existing
preferences during enrollment upserts.
- Rehydrate plain startup only after auth and client scope resolve,
without overwriting a concurrent RPC transition.
- Make ordinary `remoteControl/enable` and `remoteControl/disable`
durable while retaining `ephemeral: true` for runtime-only callers.
- Have the daemon explicitly request ephemeral enablement and regenerate
the app-server schemas.
## Verification
- Covered migration and `NULL`/`0`/`1` persistence round trips.
- Covered plain-start rehydration and runtime-only versus durable
enrollment seeding.
- Covered durable enable, durable disable, and ephemeral enable through
app-server RPC.
- Covered the daemon's exact `{ "ephemeral": true }` request payload.
Related issue: N/A (internal remote-control persistence architecture
change).
This commit is contained in:
@@ -8,6 +8,8 @@ use std::time::Duration;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
#[cfg(unix)]
|
||||
use codex_app_server_transport::REMOTE_CONTROL_DISABLED_ENV_VAR;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use tokio::fs;
|
||||
@@ -164,6 +166,9 @@ impl PidBackend {
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::from(stderr_log.into_std().await));
|
||||
if let Some((key, value)) = self.command_env() {
|
||||
command.env(key, value);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -407,6 +412,19 @@ impl PidBackend {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn command_env(&self) -> Option<(&'static str, &'static str)> {
|
||||
match self.command_kind {
|
||||
PidCommandKind::AppServer {
|
||||
remote_control_enabled: false,
|
||||
} => Some((REMOTE_CONTROL_DISABLED_ENV_VAR, "1")),
|
||||
PidCommandKind::AppServer {
|
||||
remote_control_enabled: true,
|
||||
}
|
||||
| PidCommandKind::UpdateLoop => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn terminate_process(&self, pid: u32) -> Result<()> {
|
||||
match self.command_kind {
|
||||
PidCommandKind::AppServer { .. } => terminate_process(pid),
|
||||
|
||||
@@ -3,6 +3,8 @@ use std::time::Duration;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use codex_app_server_transport::REMOTE_CONTROL_DISABLED_ENV_VAR;
|
||||
|
||||
use super::PidBackend;
|
||||
use super::PidCommandKind;
|
||||
use super::PidFileState;
|
||||
@@ -174,6 +176,24 @@ fn app_server_remote_control_uses_runtime_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_disabled_remote_control_uses_compatible_args_and_runtime_env() {
|
||||
let backend = PidBackend::new(
|
||||
"codex".into(),
|
||||
"app-server.pid".into(),
|
||||
/*remote_control_enabled*/ false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
backend.command_args(),
|
||||
vec!["app-server", "--listen", "unix://"]
|
||||
);
|
||||
assert_eq!(
|
||||
backend.command_env(),
|
||||
Some((REMOTE_CONTROL_DISABLED_ENV_VAR, "1"))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_stderr_log_tail_returns_recent_complete_lines() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
|
||||
@@ -543,6 +543,16 @@ impl Daemon {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if info.is_some() {
|
||||
match mode {
|
||||
RemoteControlMode::Enabled => {
|
||||
remote_control_client::enable_remote_control(&self.socket_path).await?;
|
||||
}
|
||||
RemoteControlMode::Disabled => {
|
||||
remote_control_client::disable_remote_control(&self.socket_path).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(self.remote_control_output(
|
||||
already_remote_control_status(mode),
|
||||
backend.map(|_| BackendKind::Pid),
|
||||
|
||||
@@ -8,9 +8,13 @@ use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCRequest;
|
||||
use codex_app_server_protocol::RemoteControlConnectionStatus;
|
||||
use codex_app_server_protocol::RemoteControlDisableParams;
|
||||
use codex_app_server_protocol::RemoteControlDisableResponse;
|
||||
use codex_app_server_protocol::RemoteControlEnableParams;
|
||||
use codex_app_server_protocol::RemoteControlEnableResponse;
|
||||
use codex_app_server_protocol::RemoteControlStatusChangedNotification;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::time::Instant;
|
||||
@@ -22,13 +26,33 @@ use crate::RemoteControlReadyStatus;
|
||||
use crate::client;
|
||||
|
||||
const REMOTE_CONTROL_READY_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const REMOTE_CONTROL_ENABLE_REQUEST_ID: RequestId = RequestId::Integer(2);
|
||||
const REMOTE_CONTROL_REQUEST_ID: RequestId = RequestId::Integer(2);
|
||||
const INVALID_PARAMS_ERROR_CODE: i64 = -32602;
|
||||
|
||||
enum RemoteControlRpcResponse<T> {
|
||||
Success(T),
|
||||
InvalidParams,
|
||||
}
|
||||
|
||||
pub(crate) async fn enable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus> {
|
||||
let mut websocket = client::connect(socket_path).await?;
|
||||
enable_remote_control_with_timeout(&mut websocket, REMOTE_CONTROL_READY_TIMEOUT).await
|
||||
}
|
||||
|
||||
pub(crate) async fn disable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus> {
|
||||
let mut websocket = client::connect(socket_path).await?;
|
||||
initialize_client(&mut websocket).await?;
|
||||
let params = serde_json::to_value(RemoteControlDisableParams { ephemeral: true })?;
|
||||
let response: RemoteControlDisableResponse = request_remote_control_with_legacy_fallback(
|
||||
&mut websocket,
|
||||
"remoteControl/disable",
|
||||
params,
|
||||
)
|
||||
.await?;
|
||||
websocket.close(None).await.ok();
|
||||
Ok(RemoteControlReadyStatus::from(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn enable_remote_control_with_connect_retry(
|
||||
socket_path: &Path,
|
||||
connect_timeout: Duration,
|
||||
@@ -43,6 +67,26 @@ async fn enable_remote_control_with_timeout<S>(
|
||||
websocket: &mut WebSocketStream<S>,
|
||||
ready_timeout: Duration,
|
||||
) -> Result<RemoteControlReadyStatus>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
initialize_client(websocket).await?;
|
||||
|
||||
let response: RemoteControlEnableResponse = request_remote_control_with_legacy_fallback(
|
||||
websocket,
|
||||
"remoteControl/enable",
|
||||
serde_json::to_value(RemoteControlEnableParams { ephemeral: true })?,
|
||||
)
|
||||
.await?;
|
||||
let mut latest = RemoteControlReadyStatus::from(response);
|
||||
if latest.status == RemoteControlConnectionStatus::Connecting {
|
||||
latest = wait_for_remote_control_status(websocket, latest, ready_timeout).await?;
|
||||
}
|
||||
websocket.close(None).await.ok();
|
||||
Ok(latest)
|
||||
}
|
||||
|
||||
async fn initialize_client<S>(websocket: &mut WebSocketStream<S>) -> Result<()>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
@@ -53,24 +97,65 @@ where
|
||||
});
|
||||
client::send_message(websocket, &initialized)
|
||||
.await
|
||||
.context("failed to send initialized notification")?;
|
||||
.context("failed to send initialized notification")
|
||||
}
|
||||
|
||||
let enable = JSONRPCMessage::Request(JSONRPCRequest {
|
||||
id: REMOTE_CONTROL_ENABLE_REQUEST_ID,
|
||||
method: "remoteControl/enable".to_string(),
|
||||
params: None,
|
||||
async fn send_remote_control_request<S>(
|
||||
websocket: &mut WebSocketStream<S>,
|
||||
request_id: RequestId,
|
||||
method: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<()>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
let request = JSONRPCMessage::Request(JSONRPCRequest {
|
||||
id: request_id,
|
||||
method: method.to_string(),
|
||||
params,
|
||||
trace: None,
|
||||
});
|
||||
client::send_message(websocket, &enable)
|
||||
client::send_message(websocket, &request)
|
||||
.await
|
||||
.context("failed to send remoteControl/enable request")?;
|
||||
.with_context(|| format!("failed to send {method} request"))
|
||||
}
|
||||
|
||||
let mut latest = read_enable_response(websocket).await?;
|
||||
if latest.status == RemoteControlConnectionStatus::Connecting {
|
||||
latest = wait_for_remote_control_status(websocket, latest, ready_timeout).await?;
|
||||
async fn request_remote_control_with_legacy_fallback<S, T>(
|
||||
websocket: &mut WebSocketStream<S>,
|
||||
method: &str,
|
||||
params: serde_json::Value,
|
||||
) -> Result<T>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
send_remote_control_request(
|
||||
websocket,
|
||||
REMOTE_CONTROL_REQUEST_ID.clone(),
|
||||
method,
|
||||
Some(params),
|
||||
)
|
||||
.await?;
|
||||
match read_remote_control_response(websocket, &REMOTE_CONTROL_REQUEST_ID, method).await? {
|
||||
RemoteControlRpcResponse::Success(response) => Ok(response),
|
||||
RemoteControlRpcResponse::InvalidParams => {
|
||||
send_remote_control_request(
|
||||
websocket,
|
||||
REMOTE_CONTROL_REQUEST_ID.clone(),
|
||||
method,
|
||||
/*params*/ None,
|
||||
)
|
||||
.await?;
|
||||
match read_remote_control_response(websocket, &REMOTE_CONTROL_REQUEST_ID, method)
|
||||
.await?
|
||||
{
|
||||
RemoteControlRpcResponse::Success(response) => Ok(response),
|
||||
RemoteControlRpcResponse::InvalidParams => {
|
||||
Err(anyhow!("{method} rejected legacy params"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
websocket.close(None).await.ok();
|
||||
Ok(latest)
|
||||
}
|
||||
|
||||
async fn connect_with_retry(
|
||||
@@ -97,11 +182,14 @@ async fn connect_with_retry(
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_enable_response<S>(
|
||||
async fn read_remote_control_response<S, T>(
|
||||
websocket: &mut WebSocketStream<S>,
|
||||
) -> Result<RemoteControlReadyStatus>
|
||||
request_id: &RequestId,
|
||||
method: &str,
|
||||
) -> Result<RemoteControlRpcResponse<T>>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
loop {
|
||||
let message = timeout(
|
||||
@@ -109,21 +197,20 @@ where
|
||||
client::read_message(websocket),
|
||||
)
|
||||
.await
|
||||
.context("timed out waiting for remoteControl/enable response")??;
|
||||
.with_context(|| format!("timed out waiting for {method} response"))??;
|
||||
match message {
|
||||
JSONRPCMessage::Response(response)
|
||||
if response.id == REMOTE_CONTROL_ENABLE_REQUEST_ID =>
|
||||
{
|
||||
let response =
|
||||
serde_json::from_value::<RemoteControlEnableResponse>(response.result)
|
||||
.context("failed to parse remoteControl/enable response")?;
|
||||
return Ok(RemoteControlReadyStatus::from(response));
|
||||
JSONRPCMessage::Response(response) if response.id == *request_id => {
|
||||
let response = serde_json::from_value::<T>(response.result)
|
||||
.with_context(|| format!("failed to parse {method} response"))?;
|
||||
return Ok(RemoteControlRpcResponse::Success(response));
|
||||
}
|
||||
JSONRPCMessage::Error(err) if err.id == REMOTE_CONTROL_ENABLE_REQUEST_ID => {
|
||||
return Err(anyhow!(
|
||||
"remoteControl/enable failed: {}",
|
||||
err.error.message
|
||||
));
|
||||
JSONRPCMessage::Error(err)
|
||||
if err.id == *request_id && err.error.code == INVALID_PARAMS_ERROR_CODE =>
|
||||
{
|
||||
return Ok(RemoteControlRpcResponse::InvalidParams);
|
||||
}
|
||||
JSONRPCMessage::Error(err) if err.id == *request_id => {
|
||||
return Err(anyhow!("{method} failed: {}", err.error.message));
|
||||
}
|
||||
JSONRPCMessage::Notification(notification)
|
||||
if remote_control_status_notification(¬ification).is_some() =>
|
||||
@@ -196,6 +283,23 @@ impl From<RemoteControlEnableResponse> for RemoteControlReadyStatus {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RemoteControlDisableResponse> for RemoteControlReadyStatus {
|
||||
fn from(response: RemoteControlDisableResponse) -> Self {
|
||||
let RemoteControlDisableResponse {
|
||||
status,
|
||||
server_name,
|
||||
installation_id: _,
|
||||
environment_id,
|
||||
} = response;
|
||||
Self {
|
||||
status,
|
||||
server_name,
|
||||
environment_id,
|
||||
timed_out: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RemoteControlStatusChangedNotification> for RemoteControlReadyStatus {
|
||||
fn from(notification: RemoteControlStatusChangedNotification) -> Self {
|
||||
let RemoteControlStatusChangedNotification {
|
||||
@@ -216,6 +320,8 @@ impl From<RemoteControlStatusChangedNotification> for RemoteControlReadyStatus {
|
||||
#[cfg(all(test, unix))]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_uds::UnixListener;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -243,6 +349,7 @@ mod tests {
|
||||
),
|
||||
after_enable_notification: None,
|
||||
ready_timeout: Duration::from_millis(20),
|
||||
reject_ephemeral_params: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -271,6 +378,7 @@ mod tests {
|
||||
Some("env_test"),
|
||||
)),
|
||||
ready_timeout: Duration::from_secs(1),
|
||||
reject_ephemeral_params: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -296,6 +404,7 @@ mod tests {
|
||||
),
|
||||
after_enable_notification: None,
|
||||
ready_timeout: Duration::from_millis(20),
|
||||
reject_ephemeral_params: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -321,6 +430,7 @@ mod tests {
|
||||
),
|
||||
after_enable_notification: None,
|
||||
ready_timeout: Duration::from_millis(20),
|
||||
reject_ephemeral_params: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -336,11 +446,104 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn enable_remote_control_retries_without_params_for_older_servers() -> Result<()> {
|
||||
let status = run_enable_remote_control_scenario(EnableScenario {
|
||||
initial_notification: None,
|
||||
enable_response: remote_control_status(
|
||||
RemoteControlConnectionStatus::Connected,
|
||||
Some("env_test"),
|
||||
),
|
||||
after_enable_notification: None,
|
||||
ready_timeout: Duration::from_millis(20),
|
||||
reject_ephemeral_params: true,
|
||||
})
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
status,
|
||||
RemoteControlReadyStatus {
|
||||
status: RemoteControlConnectionStatus::Connected,
|
||||
server_name: TEST_SERVER_NAME.to_string(),
|
||||
environment_id: Some("env_test".to_string()),
|
||||
timed_out: false,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn disable_remote_control_retries_without_params_for_older_servers() -> Result<()> {
|
||||
let dir = TempDir::new()?;
|
||||
let socket_path = dir.path().join("app-server.sock");
|
||||
let listener = UnixListener::bind(&socket_path).await?;
|
||||
let server_task = tokio::spawn(async move {
|
||||
let mut websocket = accept_initialized_client(listener).await?;
|
||||
let disable = client::read_message(&mut websocket).await?;
|
||||
let JSONRPCMessage::Request(disable) = disable else {
|
||||
panic!("expected remoteControl/disable request");
|
||||
};
|
||||
assert_eq!(disable.id, REMOTE_CONTROL_REQUEST_ID);
|
||||
assert_eq!(disable.method, "remoteControl/disable");
|
||||
assert_eq!(
|
||||
disable.params,
|
||||
Some(serde_json::json!({ "ephemeral": true }))
|
||||
);
|
||||
client::send_message(
|
||||
&mut websocket,
|
||||
&JSONRPCMessage::Error(JSONRPCError {
|
||||
id: REMOTE_CONTROL_REQUEST_ID,
|
||||
error: JSONRPCErrorError {
|
||||
code: INVALID_PARAMS_ERROR_CODE,
|
||||
message: "Invalid params".to_string(),
|
||||
data: None,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let fallback = client::read_message(&mut websocket).await?;
|
||||
let JSONRPCMessage::Request(fallback) = fallback else {
|
||||
panic!("expected fallback remoteControl/disable request");
|
||||
};
|
||||
assert_eq!(fallback.id, REMOTE_CONTROL_REQUEST_ID);
|
||||
assert_eq!(fallback.method, "remoteControl/disable");
|
||||
assert_eq!(fallback.params, None);
|
||||
client::send_message(
|
||||
&mut websocket,
|
||||
&JSONRPCMessage::Response(JSONRPCResponse {
|
||||
id: REMOTE_CONTROL_REQUEST_ID,
|
||||
result: serde_json::to_value(RemoteControlDisableResponse::from(
|
||||
remote_control_status(
|
||||
RemoteControlConnectionStatus::Disabled,
|
||||
/*environment_id*/ None,
|
||||
),
|
||||
))?,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
|
||||
let status = disable_remote_control(&socket_path).await?;
|
||||
server_task.await??;
|
||||
assert_eq!(
|
||||
status,
|
||||
RemoteControlReadyStatus {
|
||||
status: RemoteControlConnectionStatus::Disabled,
|
||||
server_name: TEST_SERVER_NAME.to_string(),
|
||||
environment_id: None,
|
||||
timed_out: false,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct EnableScenario {
|
||||
initial_notification: Option<RemoteControlStatusChangedNotification>,
|
||||
enable_response: RemoteControlStatusChangedNotification,
|
||||
after_enable_notification: Option<RemoteControlStatusChangedNotification>,
|
||||
ready_timeout: Duration,
|
||||
reject_ephemeral_params: bool,
|
||||
}
|
||||
|
||||
async fn run_enable_remote_control_scenario(
|
||||
@@ -359,12 +562,70 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn serve_enable_remote_control_scenario(
|
||||
mut listener: UnixListener,
|
||||
listener: UnixListener,
|
||||
scenario: EnableScenario,
|
||||
) -> Result<()> {
|
||||
let mut websocket = accept_initialized_client(listener).await?;
|
||||
if let Some(status) = scenario.initial_notification {
|
||||
send_remote_control_status(&mut websocket, status).await?;
|
||||
}
|
||||
|
||||
let enable = client::read_message(&mut websocket).await?;
|
||||
let JSONRPCMessage::Request(enable) = enable else {
|
||||
panic!("expected remoteControl/enable request");
|
||||
};
|
||||
assert_eq!(enable.id, REMOTE_CONTROL_REQUEST_ID);
|
||||
assert_eq!(enable.method, "remoteControl/enable");
|
||||
assert_eq!(
|
||||
enable.params,
|
||||
Some(serde_json::json!({ "ephemeral": true }))
|
||||
);
|
||||
if scenario.reject_ephemeral_params {
|
||||
client::send_message(
|
||||
&mut websocket,
|
||||
&JSONRPCMessage::Error(JSONRPCError {
|
||||
id: REMOTE_CONTROL_REQUEST_ID,
|
||||
error: JSONRPCErrorError {
|
||||
code: INVALID_PARAMS_ERROR_CODE,
|
||||
message: "Invalid params".to_string(),
|
||||
data: None,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let fallback = client::read_message(&mut websocket).await?;
|
||||
let JSONRPCMessage::Request(fallback) = fallback else {
|
||||
panic!("expected fallback remoteControl/enable request");
|
||||
};
|
||||
assert_eq!(fallback.id, REMOTE_CONTROL_REQUEST_ID);
|
||||
assert_eq!(fallback.method, "remoteControl/enable");
|
||||
assert_eq!(fallback.params, None);
|
||||
}
|
||||
client::send_message(
|
||||
&mut websocket,
|
||||
&JSONRPCMessage::Response(JSONRPCResponse {
|
||||
id: REMOTE_CONTROL_REQUEST_ID,
|
||||
result: serde_json::to_value(RemoteControlEnableResponse::from(
|
||||
scenario.enable_response,
|
||||
))?,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(status) = scenario.after_enable_notification {
|
||||
send_remote_control_status(&mut websocket, status).await?;
|
||||
} else {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn accept_initialized_client(
|
||||
mut listener: UnixListener,
|
||||
) -> Result<WebSocketStream<codex_uds::UnixStream>> {
|
||||
let stream = listener.accept().await?;
|
||||
let mut websocket = accept_async(stream).await?;
|
||||
|
||||
let initialize = client::read_message(&mut websocket).await?;
|
||||
let JSONRPCMessage::Request(initialize) = initialize else {
|
||||
panic!("expected initialize request");
|
||||
@@ -397,35 +658,7 @@ mod tests {
|
||||
panic!("expected initialized notification");
|
||||
};
|
||||
assert_eq!(initialized.method, "initialized");
|
||||
|
||||
if let Some(status) = scenario.initial_notification {
|
||||
send_remote_control_status(&mut websocket, status).await?;
|
||||
}
|
||||
|
||||
let enable = client::read_message(&mut websocket).await?;
|
||||
let JSONRPCMessage::Request(enable) = enable else {
|
||||
panic!("expected remoteControl/enable request");
|
||||
};
|
||||
assert_eq!(enable.id, REMOTE_CONTROL_ENABLE_REQUEST_ID);
|
||||
assert_eq!(enable.method, "remoteControl/enable");
|
||||
client::send_message(
|
||||
&mut websocket,
|
||||
&JSONRPCMessage::Response(JSONRPCResponse {
|
||||
id: REMOTE_CONTROL_ENABLE_REQUEST_ID,
|
||||
result: serde_json::to_value(RemoteControlEnableResponse::from(
|
||||
scenario.enable_response,
|
||||
))?,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(status) = scenario.after_enable_notification {
|
||||
send_remote_control_status(&mut websocket, status).await?;
|
||||
} else {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(websocket)
|
||||
}
|
||||
|
||||
async fn send_remote_control_status<S>(
|
||||
|
||||
Reference in New Issue
Block a user