Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ members = [
"hooks",
"secrets",
"exec",
"exec-server",
"execpolicy",
"execpolicy-legacy",
"keyring-store",
Expand Down Expand Up @@ -103,6 +104,7 @@ codex-connectors = { path = "connectors" }
codex-config = { path = "config" }
codex-core = { path = "core" }
codex-exec = { path = "exec" }
codex-exec-server = { path = "exec-server" }
codex-execpolicy = { path = "execpolicy" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-feedback = { path = "feedback" }
Expand Down
1 change: 1 addition & 0 deletions codex-rs/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ codex-utils-cli = { workspace = true }
codex-config = { workspace = true }
codex-core = { workspace = true }
codex-exec = { workspace = true }
codex-exec-server = { workspace = true }
codex-execpolicy = { workspace = true }
codex-login = { workspace = true }
codex-mcp-server = { workspace = true }
Expand Down
10 changes: 10 additions & 0 deletions codex-rs/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use codex_cloud_tasks::Cli as CloudTasksCli;
use codex_exec::Cli as ExecCli;
use codex_exec::Command as ExecCommand;
use codex_exec::ReviewArgs;
use codex_exec_server::run_main as run_exec_server_main;
use codex_execpolicy::ExecPolicyCheckCommand;
use codex_responses_api_proxy::Args as ResponsesApiProxyArgs;
use codex_state::StateRuntime;
Expand Down Expand Up @@ -144,6 +145,10 @@ enum Subcommand {
#[clap(hide = true, name = "stdio-to-uds")]
StdioToUds(StdioToUdsCommand),

/// Internal: run the exec-server stdio JSON-RPC daemon.
#[clap(hide = true, name = "exec-server")]
ExecServer,

/// Inspect feature flags.
Features(FeaturesCli),
}
Expand Down Expand Up @@ -781,6 +786,11 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
tokio::task::spawn_blocking(move || codex_stdio_to_uds::run(socket_path.as_path()))
.await??;
}
Some(Subcommand::ExecServer) => {
run_exec_server_main()
.await
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
}
Some(Subcommand::Features(FeaturesCli { sub })) => match sub {
FeaturesSubcommand::List => {
// Respect root-level `-c` overrides plus top-level flags like `--profile`.
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ codex-config = { workspace = true }
codex-shell-command = { workspace = true }
codex-skills = { workspace = true }
codex-execpolicy = { workspace = true }
codex-exec-server = { workspace = true }
codex-file-search = { workspace = true }
codex-git = { workspace = true }
codex-hooks = { workspace = true }
Expand Down
31 changes: 30 additions & 1 deletion codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@
"enable_request_compression": {
"type": "boolean"
},
"exec_permission_approvals": {
"experimental_exec_server": {
"type": "boolean"
},
"exec_permission_approvals": {
"type": "boolean"
},
Expand Down Expand Up @@ -606,6 +610,18 @@
},
"type": "object"
},
"ExecServerConfigToml": {
"additionalProperties": false,
"properties": {
"command": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"FeedbackConfigToml": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -1850,6 +1866,15 @@
"description": "When true, disables burst-paste detection for typed input entirely. All characters are inserted as they are received, and no buffering or placeholder replacement will occur for fast keypress bursts.",
"type": "boolean"
},
"exec_server": {
"allOf": [
{
"$ref": "#/definitions/ExecServerConfigToml"
}
],
"default": null,
"description": "Optional command vector used to launch the experimental exec-server."
},
"experimental_compact_prompt_file": {
"$ref": "#/definitions/AbsolutePathBuf"
},
Expand Down Expand Up @@ -1935,6 +1960,10 @@
"enable_request_compression": {
"type": "boolean"
},
"exec_permission_approvals": {
"experimental_exec_server": {
"type": "boolean"
},
"exec_permission_approvals": {
"type": "boolean"
},
Expand Down Expand Up @@ -2469,4 +2498,4 @@
},
"title": "ConfigToml",
"type": "object"
}
}
91 changes: 91 additions & 0 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ use chrono::Local;
use chrono::Utc;
use codex_app_server_protocol::McpServerElicitationRequest;
use codex_app_server_protocol::McpServerElicitationRequestParams;
use codex_exec_server::ExecServerClient;
use codex_exec_server::ExecServerLaunchCommand;
use codex_hooks::HookEvent;
use codex_hooks::HookEventAfterAgent;
use codex_hooks::HookPayload;
Expand Down Expand Up @@ -175,6 +177,82 @@ use crate::error::Result as CodexResult;
use crate::exec::StreamOutput;
use codex_config::CONFIG_TOML_FILE;

const CODEX_EXEC_SERVER_EXE_ENV_VAR: &str = "CODEX_EXEC_SERVER_EXE";

fn exec_server_launch_command_from_config(
config: &Config,
) -> CodexResult<Option<ExecServerLaunchCommand>> {
let Some(command) = config.exec_server_command.as_ref() else {
return Ok(None);
};
let Some((program, args)) = command.split_first() else {
return Err(CodexErr::Fatal(
"config [exec_server].command must not be empty".to_string(),
));
};

Ok(Some(ExecServerLaunchCommand {
program: PathBuf::from(program),
args: args.to_vec(),
}))
}

fn resolve_exec_server_launch_command(config: &Config) -> CodexResult<ExecServerLaunchCommand> {
if let Some(override_path) = std::env::var_os(CODEX_EXEC_SERVER_EXE_ENV_VAR) {
return Ok(ExecServerLaunchCommand {
program: PathBuf::from(override_path),
args: Vec::new(),
});
}

if let Some(command) = exec_server_launch_command_from_config(config)? {
return Ok(command);
}

let exec_server_binary_name = if cfg!(windows) {
"codex-exec-server.exe"
} else {
"codex-exec-server"
};
if let Ok(current_exe) = std::env::current_exe()
&& let Some(parent) = current_exe.parent()
{
let sibling = parent.join(exec_server_binary_name);
if sibling.is_file() {
return Ok(ExecServerLaunchCommand {
program: sibling,
args: Vec::new(),
});
}

let codex_binary = parent.join(if cfg!(windows) { "codex.exe" } else { "codex" });
if codex_binary.is_file() {
return Ok(ExecServerLaunchCommand {
program: codex_binary,
args: vec!["exec-server".to_string()],
});
}
}

if let Ok(program) = which::which(exec_server_binary_name) {
return Ok(ExecServerLaunchCommand {
program,
args: Vec::new(),
});
}

if let Ok(program) = which::which(if cfg!(windows) { "codex.exe" } else { "codex" }) {
return Ok(ExecServerLaunchCommand {
program,
args: vec!["exec-server".to_string()],
});
}

Err(CodexErr::Fatal(format!(
"failed to resolve exec-server binary; set {CODEX_EXEC_SERVER_EXE_ENV_VAR}, configure [exec_server].command, or install codex-exec-server"
)))
}

mod rollout_reconstruction;
#[cfg(test)]
mod rollout_reconstruction_tests;
Expand Down Expand Up @@ -1722,6 +1800,18 @@ impl Session {
});
}

let exec_server_client = if config.features.enabled(Feature::ExecServer) {
Some(Arc::new(
ExecServerClient::spawn(resolve_exec_server_launch_command(&config)?)
.await
.map_err(|err| {
CodexErr::Fatal(format!("failed to start exec-server: {err}"))
})?,
))
} else {
None
};

let services = SessionServices {
// Initialize the MCP connection manager with an uninitialized
// instance. It will be replaced with one created via
Expand All @@ -1736,6 +1826,7 @@ impl Session {
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::new(
config.background_terminal_max_timeout,
exec_server_client,
),
shell_zsh_path: config.zsh_path.clone(),
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
Expand Down
44 changes: 44 additions & 0 deletions codex-rs/core/src/codex_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ use crate::tools::registry::ToolHandler;
use crate::tools::router::ToolCallSource;
use crate::turn_diff_tracker::TurnDiffTracker;
use codex_app_server_protocol::AppInfo;
use codex_exec_server::ExecServerLaunchCommand;
use codex_otel::TelemetryAuthMode;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::ContentItem;
Expand Down Expand Up @@ -1777,6 +1778,47 @@ async fn build_test_config(codex_home: &Path) -> Config {
.expect("load default test config")
}

#[test]
fn exec_server_launch_command_uses_config_command() {
let mut config = test_config();
config.exec_server_command = Some(vec![
"ssh".to_string(),
"-T".to_string(),
"executor-host".to_string(),
"/opt/codex-exec-server".to_string(),
]);

let command = exec_server_launch_command_from_config(&config)
.expect("config command should parse")
.expect("config command should exist");

assert_eq!(
ExecServerLaunchCommand {
program: PathBuf::from("ssh"),
args: vec![
"-T".to_string(),
"executor-host".to_string(),
"/opt/codex-exec-server".to_string(),
],
},
command
);
}

#[test]
fn exec_server_launch_command_rejects_empty_config_command() {
let mut config = test_config();
config.exec_server_command = Some(Vec::new());

let err = exec_server_launch_command_from_config(&config)
.expect_err("empty config command should fail");

assert_eq!(
err.to_string(),
"Fatal error: config [exec_server].command must not be empty"
);
}

fn session_telemetry(
conversation_id: ThreadId,
config: &Config,
Expand Down Expand Up @@ -2123,6 +2165,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::new(
config.background_terminal_max_timeout,
None,
),
shell_zsh_path: None,
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
Expand Down Expand Up @@ -2795,6 +2838,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::new(
config.background_terminal_max_timeout,
None,
),
shell_zsh_path: None,
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
Expand Down
Loading
Loading