diff --git a/codex-rs/core/src/environment_selection.rs b/codex-rs/core/src/environment_selection.rs index d6804c02ae..8530625d0d 100644 --- a/codex-rs/core/src/environment_selection.rs +++ b/codex-rs/core/src/environment_selection.rs @@ -138,6 +138,7 @@ struct ResolvedEnvironment { environment: Arc, shell: Option, user_home_dir: Option, + executor_platform_os: Option, temporary_directories: Option>, shell_snapshot: ShellSnapshotTask, shell_snapshot_v2_supported: bool, @@ -259,6 +260,7 @@ impl ThreadEnvironments { environment: environment.environment, shell: environment.shell, user_home_dir: environment.user_home_dir, + executor_platform_os: environment.executor_platform_os, temporary_directories: environment.temporary_directories, shell_snapshot: environment.shell_snapshot, shell_snapshot_v2_supported: environment.shell_snapshot_v2_supported, @@ -625,9 +627,11 @@ impl ThreadEnvironments { }; // Resolve the attachment only after both prerequisites are ready. let ((), installed_config) = tokio::try_join!(connection_ready, configuration_ready)?; + let executor_platform_os; let (shell, user_home_dir, temporary_dirs, snapshot_v2) = if environment.is_remote() { match environment.info().await { Ok(info) => { + executor_platform_os = info.platform_os; let user_home_dir = info.user_home_dir; let temporary_directories = info.temporary_directories; let shell_snapshot_v2_supported = info.capabilities.shell_snapshot_v2; @@ -648,11 +652,13 @@ impl ThreadEnvironments { ) } Err(err) => { + executor_platform_os = None; tracing::warn!("failed to get info for environment `{environment_id}`: {err}"); (None, None, None, false) } } } else { + executor_platform_os = Some(std::env::consts::OS.to_string()); ( Some(local_shell), PathUri::from_host_native_path("~").ok(), @@ -671,6 +677,7 @@ impl ThreadEnvironments { environment, shell, user_home_dir, + executor_platform_os, temporary_directories: temporary_dirs, shell_snapshot: task, shell_snapshot_v2_supported: snapshot_v2, @@ -748,6 +755,7 @@ impl TurnEnvironmentState { environment.environment, environment.shell, ); + turn_environment.executor_platform_os = environment.executor_platform_os; turn_environment.shell_snapshot = environment.shell_snapshot; turn_environment.shell_snapshot_v2_supported = environment.shell_snapshot_v2_supported; @@ -1029,6 +1037,7 @@ mod tests { "result": { "shell": { "name": "zsh", "path": "/bin/zsh" }, "userHomeDir": "file:///home/remote", + "platformOs": "windows", "temporaryDirectories": ["file:///tmp/remote"], } }) @@ -1483,6 +1492,13 @@ url = "ws://127.0.0.1:8765" .collect::>(), vec![expected_config.clone(), expected_config] ); + assert_eq!( + attached + .turn_environments() + .map(|environment| environment.executor_platform_os.as_deref()) + .collect::>(), + vec![Some("windows"), Some(std::env::consts::OS)] + ); assert_eq!(attached.to_selections(), vec![remote, local]); let environment = attached.primary().expect("remote environment"); let expected_user_home_dir = PathUri::parse("file:///home/remote").expect("remote home"); diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 7a4184e9f1..cc3681d420 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -53,6 +53,8 @@ pub(crate) struct TurnEnvironment { /// Cached from the selected executor; `None` means it did not report them. pub(crate) temporary_directories: Option>, pub(crate) shell: Option, + /// OS reported by the selected executor; `None` for legacy executors. + pub(crate) executor_platform_os: Option, pub(crate) shell_snapshot: ShellSnapshotTask, pub(crate) shell_snapshot_v2_supported: bool, } @@ -72,6 +74,7 @@ impl TurnEnvironment { user_home_dir: None, temporary_directories: None, shell, + executor_platform_os: None, shell_snapshot: futures::future::ready(None).boxed().shared(), shell_snapshot_v2_supported: false, } @@ -172,6 +175,7 @@ impl std::fmt::Debug for TurnEnvironment { .field("user_home_dir", &self.user_home_dir) .field("temporary_directories", &self.temporary_directories) .field("shell", &self.shell) + .field("executor_platform_os", &self.executor_platform_os) .field("config", self.config()) .field("config_origin", &self.config_origin) .finish_non_exhaustive() diff --git a/codex-rs/exec-server-protocol/src/protocol.rs b/codex-rs/exec-server-protocol/src/protocol.rs index 5cc016fe70..1eadc29770 100644 --- a/codex-rs/exec-server-protocol/src/protocol.rs +++ b/codex-rs/exec-server-protocol/src/protocol.rs @@ -100,6 +100,9 @@ pub struct EnvironmentInfo { /// Executor user home used to expand `~` in path-bearing values. #[serde(default, skip_serializing_if = "Option::is_none")] pub user_home_dir: Option, + /// Operating system reported by the executor; absent for legacy exec-servers. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platform_os: Option, /// Executor-local default directories for resolving `:tmpdir`, when reported. /// On Windows, a command's `TEMP` or `TMP` overrides take precedence. #[serde(default, skip_serializing_if = "Option::is_none")] @@ -213,6 +216,7 @@ impl EnvironmentInfo { shell: codex_shell_command::shell_detect::default_user_shell().into(), cwd: cwd.and_then(|cwd| PathUri::from_host_native_path(cwd).ok()), user_home_dir: PathUri::from_host_native_path("~").ok(), + platform_os: Some(std::env::consts::OS.to_string()), temporary_directories: Some(temporary_directories), temp_dir, capabilities: EnvironmentCapabilities { @@ -978,6 +982,7 @@ mod tests { }, cwd: None, user_home_dir: None, + platform_os: None, temporary_directories: None, temp_dir: None, capabilities: EnvironmentCapabilities::default(), @@ -1012,6 +1017,7 @@ mod tests { "shell": { "name": "powershell", "path": "powershell.exe" }, "cwd": null, "userHomeDir": "file:///C:/Users/remote", + "platformOs": "windows", "temporaryDirectories": ["file:///C:/Temp", "file:///D:/Temp"], "capabilities": { "networkProxyLaunch": false,