Update codex remote-control to start the daemon (#22218)

## Why
Update `codex remote-control` to use the new app server daemon commands
instead.
- if the updater loop is not running, bootstrap the daemon with remote
control enabled (`codex app-server daemon bootstrap --remote-control`)
- otherwise, enable the persisted remote-control setting and start the
daemon normally
This commit is contained in:
Owen Lin
2026-05-11 15:38:30 -07:00
committed by GitHub
Unverified
parent 9ab7f4e6ac
commit 4859d80ffe
3 changed files with 92 additions and 38 deletions
+4
View File
@@ -92,6 +92,10 @@ JSON-RPC initialize handshake on the Unix control socket.
for future starts. If a managed app-server is already running, they restart it
so the new setting takes effect immediately.
Top-level `codex remote-control` bootstraps with `--remote-control` when the
updater loop is not running. Otherwise it enables remote control and starts the
daemon normally.
`stop` sends a graceful termination request first, then sends a second
termination signal after the grace window if the process is still alive.
+85 -1
View File
@@ -89,6 +89,13 @@ pub struct BootstrapOutput {
pub app_server_version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum RemoteControlStartOutput {
Bootstrap(BootstrapOutput),
Start(LifecycleOutput),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemoteControlMode {
Enabled,
@@ -165,6 +172,13 @@ pub async fn bootstrap(options: BootstrapOptions) -> Result<BootstrapOutput> {
Daemon::from_environment()?.bootstrap(options).await
}
pub async fn ensure_remote_control_started() -> Result<RemoteControlStartOutput> {
ensure_supported_platform()?;
Daemon::from_environment()?
.ensure_remote_control_started()
.await
}
pub async fn set_remote_control(mode: RemoteControlMode) -> Result<RemoteControlOutput> {
ensure_supported_platform()?;
Daemon::from_environment()?.set_remote_control(mode).await
@@ -397,8 +411,34 @@ impl Daemon {
self.bootstrap_locked(options).await
}
async fn ensure_remote_control_started(&self) -> Result<RemoteControlStartOutput> {
let _operation_lock = self.acquire_operation_lock().await?;
let settings = self.load_settings().await?;
if self.is_bootstrapped(&settings).await? {
let _ = self
.set_remote_control_locked(RemoteControlMode::Enabled)
.await?;
let output = self.start().await?;
return Ok(RemoteControlStartOutput::Start(output));
}
let output = self
.bootstrap_locked(BootstrapOptions {
remote_control_enabled: true,
})
.await?;
Ok(RemoteControlStartOutput::Bootstrap(output))
}
async fn set_remote_control(&self, mode: RemoteControlMode) -> Result<RemoteControlOutput> {
let _operation_lock = self.acquire_operation_lock().await?;
self.set_remote_control_locked(mode).await
}
async fn set_remote_control_locked(
&self,
mode: RemoteControlMode,
) -> Result<RemoteControlOutput> {
let previous_settings = self.load_settings().await?;
let mut settings = previous_settings.clone();
let remote_control_enabled = mode.is_enabled();
@@ -517,6 +557,11 @@ impl Daemon {
backend.start().await
}
async fn is_bootstrapped(&self, settings: &DaemonSettings) -> Result<bool> {
let updater = backend::pid_update_loop_backend(self.backend_paths(settings));
updater.is_starting_or_running().await
}
fn ensure_managed_codex_bin(&self) -> Result<()> {
if self.managed_codex_bin.is_file() {
return Ok(());
@@ -687,8 +732,12 @@ fn try_lock_file(_file: &tokio::fs::File) -> Result<bool> {
mod tests {
use pretty_assertions::assert_eq;
use super::BackendKind;
use super::BootstrapOutput;
use super::BootstrapStatus;
use super::LifecycleOutput;
use super::LifecycleStatus;
use super::RemoteControlStartOutput;
use super::RemoteControlStatus;
use super::RestartDecision;
use super::RestartIfRunningOutcome;
@@ -776,7 +825,7 @@ mod tests {
restart_decision(
RestartMode::Always,
/*info*/ None,
/*managed_version*/ None
/*managed_version*/ None,
),
],
[
@@ -787,4 +836,39 @@ mod tests {
]
);
}
#[test]
fn remote_control_start_output_serializes_inner_output_without_tag() {
let lifecycle_output = LifecycleOutput {
status: LifecycleStatus::AlreadyRunning,
backend: Some(BackendKind::Pid),
pid: None,
socket_path: "codex.sock".into(),
cli_version: Some("1.2.3".to_string()),
app_server_version: Some("1.2.4".to_string()),
};
let output = RemoteControlStartOutput::Start(lifecycle_output.clone());
assert_eq!(
serde_json::to_value(output).expect("serialize"),
serde_json::to_value(lifecycle_output).expect("serialize")
);
let bootstrap_output = BootstrapOutput {
status: BootstrapStatus::Bootstrapped,
backend: BackendKind::Pid,
auto_update_enabled: true,
remote_control_enabled: true,
managed_codex_path: "codex".into(),
socket_path: "codex.sock".into(),
cli_version: "1.2.3".to_string(),
app_server_version: "1.2.4".to_string(),
};
let output = RemoteControlStartOutput::Bootstrap(bootstrap_output.clone());
assert_eq!(
serde_json::to_value(output).expect("serialize"),
serde_json::to_value(bootstrap_output).expect("serialize")
);
}
}
+3 -37
View File
@@ -129,7 +129,7 @@ enum Subcommand {
/// [experimental] Run the app server or related tooling.
AppServer(AppServerCommand),
/// [experimental] Start a headless app-server with remote control enabled.
/// [experimental] Ensure the app-server daemon is running with remote control enabled.
RemoteControl,
/// Launch the Codex desktop app (opens the app installer if missing).
@@ -772,14 +772,6 @@ struct FeatureSetArgs {
feature: String,
}
const REMOTE_CONTROL_FEATURE_OVERRIDE: &str = "features.remote_control=true";
fn enable_remote_control_for_invocation(config_overrides: &mut CliConfigOverrides) {
config_overrides
.raw_overrides
.push(REMOTE_CONTROL_FEATURE_OVERRIDE.to_string());
}
fn stage_str(stage: Stage) -> &'static str {
match stage {
Stage::UnderDevelopment => "under development",
@@ -989,16 +981,8 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
root_remote_auth_token_env.as_deref(),
"remote-control",
)?;
enable_remote_control_for_invocation(&mut root_config_overrides);
codex_app_server::run_main_with_transport(
arg0_paths.clone(),
root_config_overrides,
codex_config::LoaderOverrides::default(),
/*default_analytics_enabled*/ false,
codex_app_server::AppServerTransport::Off,
codex_protocol::protocol::SessionSource::Cli,
)
.await?;
let output = codex_app_server_daemon::ensure_remote_control_started().await?;
println!("{}", serde_json::to_string(&output)?);
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
Some(Subcommand::App(app_cli)) => {
@@ -2408,24 +2392,6 @@ mod tests {
assert!(app_server.analytics_default_enabled);
}
#[test]
fn remote_control_override_is_appended_after_root_toggles() {
let mut config_overrides = CliConfigOverrides::default();
config_overrides
.raw_overrides
.push("features.remote_control=false".to_string());
enable_remote_control_for_invocation(&mut config_overrides);
assert_eq!(
config_overrides.raw_overrides,
vec![
"features.remote_control=false".to_string(),
REMOTE_CONTROL_FEATURE_OVERRIDE.to_string(),
]
);
}
#[test]
fn reject_remote_flag_for_remote_control() {
let cli = MultitoolCli::try_parse_from([