Run code mode exclusively through the standalone host (#36217)

## What changed

- Move the V8 implementation into a dedicated `codex-code-mode-runtime` crate used by `codex-code-mode-host`, removing the embedded runtime fallback from the Codex process.
- Resolve the host executable from the active installation layout and check its availability before selecting tools.
- Fall back to direct tools with a one-time warning when optional code mode is unavailable. Keep `code_mode_only` and `disable_in_process_fallback` configurations fail-closed.

## Testing

- Cover host discovery for standalone and package layouts, including missing hosts and symlinks.
- Verify direct-tool fallback, one-time warnings, and fail-closed code-mode-only behavior.

GitOrigin-RevId: 5aa3c6f1db148b2231fc24089a2ee0e2b00dbddb
This commit is contained in:
Channing Conger
2026-07-30 20:24:29 +00:00
committed by copyberry
parent acd540f158
commit 97576b1794
51 changed files with 627 additions and 424 deletions
+41 -79
View File
@@ -1,4 +1,3 @@
use std::ffi::OsString;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
@@ -20,6 +19,7 @@ use codex_code_mode_protocol::WaitRequest;
use codex_code_mode_protocol::host::SessionId;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_install_context::InstallContext;
use tokio::sync::Semaphore;
use tokio::sync::watch;
@@ -31,87 +31,74 @@ use crate::NoopCodeModeSessionDelegate;
mod connection;
const CODE_MODE_HOST_PATH_ENV: &str = "CODEX_CODE_MODE_HOST_PATH";
type ShutdownResultReceiver = watch::Receiver<Option<Result<(), String>>>;
/// Creates code-mode sessions backed by one lazily spawned process host.
pub struct ProcessOwnedCodeModeSessionProvider {
state: StdMutex<ProviderState>,
allow_in_process_fallback: bool,
host: Arc<OwnedCodeModeHost>,
}
/// Rejects code-mode sessions when the standalone host is disabled.
#[derive(Default)]
pub struct DisabledCodeModeSessionProvider;
/// Creates code-mode sessions backed by one shared remote WebSocket connection.
pub struct WebSocketCodeModeSessionProvider {
host: Arc<OwnedCodeModeHost>,
}
enum ProviderState {
OwnedProcess(Arc<OwnedCodeModeHost>),
InProcess,
}
impl ProcessOwnedCodeModeSessionProvider {
pub fn with_host_program(host_program: PathBuf) -> Self {
Self {
state: StdMutex::new(ProviderState::OwnedProcess(Arc::new(
OwnedCodeModeHost::new(host_program),
))),
allow_in_process_fallback: true,
host: Arc::new(OwnedCodeModeHost::new(host_program)),
}
}
pub fn without_in_process_fallback(mut self) -> Self {
self.allow_in_process_fallback = false;
self
}
fn process_host(&self) -> Option<Arc<OwnedCodeModeHost>> {
match &*self
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
{
ProviderState::OwnedProcess(process_host) => Some(Arc::clone(process_host)),
ProviderState::InProcess => None,
}
fn process_host(&self) -> Arc<OwnedCodeModeHost> {
Arc::clone(&self.host)
}
}
impl Default for ProcessOwnedCodeModeSessionProvider {
fn default() -> Self {
Self::with_host_program(default_host_program())
Self::with_host_program(InstallContext::current().code_mode_host_program())
}
}
impl CodeModeSessionProvider for ProcessOwnedCodeModeSessionProvider {
fn availability(&self) -> Result<(), String> {
let HostEndpoint::Process(host_program) = &self.host.endpoint else {
unreachable!("a process-owned provider always has a process endpoint");
};
if host_program.is_file() {
Ok(())
} else {
Err(ConnectionError::Spawn {
host_program: host_program.clone(),
error: io::Error::new(io::ErrorKind::NotFound, "host executable was not found"),
}
.to_string())
}
}
fn create_session<'a>(
&'a self,
delegate: Arc<dyn CodeModeSessionDelegate>,
) -> CodeModeSessionProviderFuture<'a> {
Box::pin(async move {
let Some(process_host) = self.process_host() else {
let session: Arc<dyn CodeModeSession> =
Arc::new(crate::InProcessCodeModeSession::with_delegate(delegate));
return Ok(session);
};
Box::pin(create_host_session(delegate, self.process_host()))
}
}
match process_host.connection().await {
Ok(_) => {}
Err(error) if error.host_program_not_found() && self.allow_in_process_fallback => {
*self
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
ProviderState::InProcess;
let session: Arc<dyn CodeModeSession> =
Arc::new(crate::InProcessCodeModeSession::with_delegate(delegate));
return Ok(session);
}
Err(error) => return Err(error.to_string()),
}
create_host_session(delegate, process_host).await
})
impl CodeModeSessionProvider for DisabledCodeModeSessionProvider {
fn availability(&self) -> Result<(), String> {
Err("code-mode host is disabled".to_string())
}
fn create_session<'a>(
&'a self,
_delegate: Arc<dyn CodeModeSessionDelegate>,
) -> CodeModeSessionProviderFuture<'a> {
Box::pin(async { Err("code-mode host is disabled".to_string()) })
}
}
@@ -273,7 +260,9 @@ impl ProcessOwnedCodeModeSession {
pub fn new() -> Self {
Self::with_host(
Arc::new(NoopCodeModeSessionDelegate),
Arc::new(OwnedCodeModeHost::new(default_host_program())),
Arc::new(OwnedCodeModeHost::new(
InstallContext::current().code_mode_host_program(),
)),
)
}
@@ -569,33 +558,6 @@ impl CodeModeSession for ProcessOwnedCodeModeSession {
}
}
fn default_host_program() -> PathBuf {
resolve_host_program(
std::env::var_os(CODE_MODE_HOST_PATH_ENV),
std::env::current_exe(),
)
}
fn resolve_host_program(
override_path: Option<OsString>,
current_exe: io::Result<PathBuf>,
) -> PathBuf {
if let Some(path) = override_path {
return PathBuf::from(path);
}
let executable_name = if cfg!(windows) {
"codex-code-mode-host.exe"
} else {
"codex-code-mode-host"
};
if let Ok(current_exe) = current_exe
&& let Some(parent) = current_exe.parent()
{
return parent.join(executable_name);
}
PathBuf::from(executable_name)
}
#[cfg(test)]
#[path = "remote_session_tests.rs"]
mod tests;