diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 32b41c0227..d00aa45456 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1664,6 +1664,7 @@ dependencies = [ "codex-rmcp-client", "codex-state", "codex-stdio-to-uds", + "codex-terminal-detection", "codex-tui", "codex-tui-app-server", "codex-utils-cargo-bin", @@ -1858,6 +1859,7 @@ dependencies = [ "codex-shell-escalation", "codex-skills", "codex-state", + "codex-terminal-detection", "codex-test-macros", "codex-utils-absolute-path", "codex-utils-cache", @@ -2507,6 +2509,14 @@ dependencies = [ "uds_windows", ] +[[package]] +name = "codex-terminal-detection" +version = "0.0.0" +dependencies = [ + "pretty_assertions", + "tracing", +] + [[package]] name = "codex-test-macros" version = "0.0.0" @@ -2542,6 +2552,7 @@ dependencies = [ "codex-protocol", "codex-shell-command", "codex-state", + "codex-terminal-detection", "codex-tui-app-server", "codex-utils-absolute-path", "codex-utils-approval-presets", @@ -2633,6 +2644,7 @@ dependencies = [ "codex-protocol", "codex-shell-command", "codex-state", + "codex-terminal-detection", "codex-utils-absolute-path", "codex-utils-approval-presets", "codex-utils-cargo-bin", @@ -5815,6 +5827,7 @@ dependencies = [ "anyhow", "codex-core", "codex-mcp-server", + "codex-terminal-detection", "codex-utils-cargo-bin", "core_test_support", "os_info", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index edcd98fbb1..961c4ef9f9 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -66,6 +66,7 @@ members = [ "codex-client", "codex-api", "state", + "terminal-detection", "codex-experimental-api-macros", "test-macros", "package-manager", @@ -131,6 +132,7 @@ codex-skills = { path = "skills" } codex-state = { path = "state" } codex-stdio-to-uds = { path = "stdio-to-uds" } codex-test-macros = { path = "test-macros" } +codex-terminal-detection = { path = "terminal-detection" } codex-tui = { path = "tui" } codex-tui-app-server = { path = "tui_app_server" } codex-utils-absolute-path = { path = "utils/absolute-path" } diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index a7e88cd1b4..affc5ef8c2 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -37,6 +37,7 @@ codex-responses-api-proxy = { workspace = true } codex-rmcp-client = { workspace = true } codex-state = { workspace = true } codex-stdio-to-uds = { workspace = true } +codex-terminal-detection = { workspace = true } codex-tui = { workspace = true } codex-tui-app-server = { workspace = true } libc = { workspace = true } diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 9386398284..e9f4d6f686 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -50,7 +50,7 @@ use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::find_codex_home; use codex_core::features::Stage; use codex_core::features::is_known_feature_key; -use codex_core::terminal::TerminalName; +use codex_terminal_detection::TerminalName; /// Codex CLI /// @@ -1049,7 +1049,7 @@ async fn run_interactive_tui( interactive.prompt = Some(prompt.replace("\r\n", "\n").replace('\r', "\n")); } - let terminal_info = codex_core::terminal::terminal_info(); + let terminal_info = codex_terminal_detection::terminal_info(); if terminal_info.name == TerminalName::Dumb { if !(std::io::stdin().is_terminal() && std::io::stderr().is_terminal()) { return Ok(AppExitInfo::fatal( diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index a44da5e111..869f9dd9f4 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -48,6 +48,7 @@ codex-artifacts = { workspace = true } codex-protocol = { workspace = true } codex-rmcp-client = { workspace = true } codex-state = { workspace = true } +codex-terminal-detection = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-cache = { workspace = true } codex-utils-image = { workspace = true } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 9e76297a0d..c248c0f0de 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -49,7 +49,6 @@ use crate::stream_events_utils::handle_output_item_done; use crate::stream_events_utils::last_assistant_message_from_item; use crate::stream_events_utils::raw_assistant_output_text_from_item; use crate::stream_events_utils::record_completed_response_item; -use crate::terminal; use crate::truncate::TruncationPolicy; use crate::turn_metadata::TurnMetadataState; use crate::util::error_or_panic; @@ -118,6 +117,7 @@ use codex_protocol::request_user_input::RequestUserInputArgs; use codex_protocol::request_user_input::RequestUserInputResponse; use codex_rmcp_client::ElicitationResponse; use codex_rmcp_client::OAuthCredentialsStoreMode; +use codex_terminal_detection::user_agent; use codex_utils_stream_parser::AssistantTextChunk; use codex_utils_stream_parser::AssistantTextStreamParser; use codex_utils_stream_parser::ProposedPlanSegment; @@ -1594,7 +1594,7 @@ impl Session { let account_id = auth.and_then(CodexAuth::get_account_id); let account_email = auth.and_then(CodexAuth::get_account_email); let originator = crate::default_client::originator().value; - let terminal_type = terminal::user_agent(); + let terminal_type = user_agent(); let session_model = session_configuration.collaboration_mode.model().to_string(); let auth_env_telemetry = collect_auth_env_telemetry( &session_configuration.provider, diff --git a/codex-rs/core/src/default_client.rs b/codex-rs/core/src/default_client.rs index 3ca653ba46..59c7bd2fb9 100644 --- a/codex-rs/core/src/default_client.rs +++ b/codex-rs/core/src/default_client.rs @@ -4,6 +4,7 @@ use codex_client::BuildCustomCaTransportError; use codex_client::CodexHttpClient; pub use codex_client::CodexRequestBuilder; use codex_client::build_reqwest_client_with_custom_ca; +use codex_terminal_detection::user_agent; use reqwest::header::HeaderMap; use reqwest::header::HeaderValue; use std::sync::LazyLock; @@ -130,7 +131,7 @@ pub fn get_codex_user_agent() -> String { os_info.os_type(), os_info.version(), os_info.architecture().unwrap_or("unknown"), - crate::terminal::user_agent() + user_agent() ); let suffix = USER_AGENT_SUFFIX .lock() diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 10a51b23ec..6d519f488f 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -120,7 +120,6 @@ pub mod shell_snapshot; pub mod skills; pub mod spawn; pub mod state_db; -pub mod terminal; mod tools; pub mod turn_diff_tracker; mod turn_metadata; diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 8992eb4456..a91e95bbe5 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -587,6 +587,12 @@ fn create_request_permissions_schema() -> JsonSchema { } } +fn windows_destructive_filesystem_guidance() -> &'static str { + r#"Windows safety rules: +- Do not compose destructive filesystem commands across shells. Do not enumerate paths in PowerShell and then pass them to `cmd /c`, batch builtins, or another shell for deletion or moving. Use one shell end-to-end, prefer native PowerShell cmdlets such as `Remove-Item` / `Move-Item` with `-LiteralPath`, and avoid string-built shell commands for file operations. +- Before any recursive delete or move on Windows, verify the resolved absolute target paths stay within the intended workspace or explicitly named target directory. Never issue a recursive delete or move against a computed path if the final target has not been checked."# +} + fn create_approval_parameters( exec_permission_approvals_enabled: bool, ) -> BTreeMap { @@ -709,9 +715,15 @@ fn create_exec_command_tool( ToolSpec::Function(ResponsesApiTool { name: "exec_command".to_string(), - description: + description: if cfg!(windows) { + format!( + "Runs a command in a PTY, returning output or a session ID for ongoing interaction.\n\n{}", + windows_destructive_filesystem_guidance() + ) + } else { "Runs a command in a PTY, returning output or a session ID for ongoing interaction." - .to_string(), + .to_string() + }, strict: false, defer_loading: None, parameters: JsonSchema::Object { @@ -848,22 +860,28 @@ fn create_shell_tool(exec_permission_approvals_enabled: bool) -> ToolSpec { exec_permission_approvals_enabled, )); - let description = if cfg!(windows) { - r#"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with ["powershell.exe", "-Command"]. + let description = if cfg!(windows) { + format!( + r#"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with ["powershell.exe", "-Command"]. Examples of valid command strings: - ls -a (show hidden): ["powershell.exe", "-Command", "Get-ChildItem -Force"] - recursive find by name: ["powershell.exe", "-Command", "Get-ChildItem -Recurse -Filter *.py"] - recursive grep: ["powershell.exe", "-Command", "Get-ChildItem -Path C:\\myrepo -Recurse | Select-String -Pattern 'TODO' -CaseSensitive"] -- ps aux | grep python: ["powershell.exe", "-Command", "Get-Process | Where-Object { $_.ProcessName -like '*python*' }"] +- ps aux | grep python: ["powershell.exe", "-Command", "Get-Process | Where-Object {{ $_.ProcessName -like '*python*' }}"] - setting an env var: ["powershell.exe", "-Command", "$env:FOO='bar'; echo $env:FOO"] -- running an inline Python script: ["powershell.exe", "-Command", "@'\\nprint('Hello, world!')\\n'@ | python -"]"# +- running an inline Python script: ["powershell.exe", "-Command", "@'\\nprint('Hello, world!')\\n'@ | python -"] + +{}"#, + windows_destructive_filesystem_guidance() + ) } else { r#"Runs a shell command and returns its output. - The arguments to `shell` will be passed to execvp(). Most terminal commands should be prefixed with ["bash", "-lc"]. - Always set the `workdir` param when using the shell function. Do not use `cd` unless absolutely necessary."# - }.to_string(); + .to_string() + }; ToolSpec::Function(ResponsesApiTool { name: "shell".to_string(), @@ -921,20 +939,26 @@ fn create_shell_command_tool( )); let description = if cfg!(windows) { - r#"Runs a Powershell command (Windows) and returns its output. + format!( + r#"Runs a Powershell command (Windows) and returns its output. Examples of valid command strings: - ls -a (show hidden): "Get-ChildItem -Force" - recursive find by name: "Get-ChildItem -Recurse -Filter *.py" - recursive grep: "Get-ChildItem -Path C:\\myrepo -Recurse | Select-String -Pattern 'TODO' -CaseSensitive" -- ps aux | grep python: "Get-Process | Where-Object { $_.ProcessName -like '*python*' }" +- ps aux | grep python: "Get-Process | Where-Object {{ $_.ProcessName -like '*python*' }}" - setting an env var: "$env:FOO='bar'; echo $env:FOO" -- running an inline Python script: "@'\\nprint('Hello, world!')\\n'@ | python -"# +- running an inline Python script: "@'\\nprint('Hello, world!')\\n'@ | python -" + +{}"#, + windows_destructive_filesystem_guidance() + ) } else { r#"Runs a shell command and returns its output. - Always set the `workdir` param when using the shell_command function. Do not use `cd` unless absolutely necessary."# - }.to_string(); + .to_string() + }; ToolSpec::Function(ResponsesApiTool { name: "shell_command".to_string(), diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index 2d0a23431c..3142dd46a3 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -50,6 +50,10 @@ fn discoverable_connector(id: &str, name: &str, description: &str) -> Discoverab })) } +fn windows_shell_safety_description() -> String { + format!("\n\n{}", super::windows_destructive_filesystem_guidance()) +} + fn search_capable_model_info() -> ModelInfo { let config = test_config(); let mut model_info = @@ -2363,7 +2367,7 @@ fn test_shell_tool() { assert_eq!(name, "shell"); let expected = if cfg!(windows) { - r#"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with ["powershell.exe", "-Command"]. + r#"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with ["powershell.exe", "-Command"]. Examples of valid command strings: @@ -2373,11 +2377,37 @@ Examples of valid command strings: - ps aux | grep python: ["powershell.exe", "-Command", "Get-Process | Where-Object { $_.ProcessName -like '*python*' }"] - setting an env var: ["powershell.exe", "-Command", "$env:FOO='bar'; echo $env:FOO"] - running an inline Python script: ["powershell.exe", "-Command", "@'\\nprint('Hello, world!')\\n'@ | python -"]"# - } else { - r#"Runs a shell command and returns its output. + .to_string() + + &windows_shell_safety_description() + } else { + r#"Runs a shell command and returns its output. - The arguments to `shell` will be passed to execvp(). Most terminal commands should be prefixed with ["bash", "-lc"]. - Always set the `workdir` param when using the shell function. Do not use `cd` unless absolutely necessary."# - }.to_string(); + .to_string() + }; + assert_eq!(description, &expected); +} + +#[test] +fn test_exec_command_tool_windows_description_includes_shell_safety_guidance() { + let tool = super::create_exec_command_tool(true, false); + let ToolSpec::Function(ResponsesApiTool { + description, name, .. + }) = &tool + else { + panic!("expected function tool"); + }; + assert_eq!(name, "exec_command"); + + let expected = if cfg!(windows) { + format!( + "Runs a command in a PTY, returning output or a session ID for ongoing interaction.{}", + windows_shell_safety_description() + ) + } else { + "Runs a command in a PTY, returning output or a session ID for ongoing interaction." + .to_string() + }; assert_eq!(description, &expected); } @@ -2482,7 +2512,9 @@ Examples of valid command strings: - recursive grep: "Get-ChildItem -Path C:\\myrepo -Recurse | Select-String -Pattern 'TODO' -CaseSensitive" - ps aux | grep python: "Get-Process | Where-Object { $_.ProcessName -like '*python*' }" - setting an env var: "$env:FOO='bar'; echo $env:FOO" -- running an inline Python script: "@'\\nprint('Hello, world!')\\n'@ | python -"#.to_string() +- running an inline Python script: "@'\\nprint('Hello, world!')\\n'@ | python -""# + .to_string() + + &windows_shell_safety_description() } else { r#"Runs a shell command and returns its output. - Always set the `workdir` param when using the shell_command function. Do not use `cd` unless absolutely necessary."#.to_string() diff --git a/codex-rs/exec-server/src/server/filesystem.rs b/codex-rs/exec-server/src/server/filesystem.rs index bc3d22a4da..a263bb1fee 100644 --- a/codex-rs/exec-server/src/server/filesystem.rs +++ b/codex-rs/exec-server/src/server/filesystem.rs @@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem { impl Default for ExecServerFileSystem { fn default() -> Self { Self { - file_system: Arc::new(Environment.get_filesystem()), + file_system: Arc::new(Environment::default().get_filesystem()), } } } diff --git a/codex-rs/mcp-server/tests/common/Cargo.toml b/codex-rs/mcp-server/tests/common/Cargo.toml index 1dec2d09ac..83f2c53697 100644 --- a/codex-rs/mcp-server/tests/common/Cargo.toml +++ b/codex-rs/mcp-server/tests/common/Cargo.toml @@ -11,6 +11,7 @@ path = "lib.rs" anyhow = { workspace = true } codex-core = { workspace = true } codex-mcp-server = { workspace = true } +codex-terminal-detection = { workspace = true } codex-utils-cargo-bin = { workspace = true } rmcp = { workspace = true } os_info = { workspace = true } diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 92b5caa65a..53925ca396 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -11,6 +11,7 @@ use tokio::process::ChildStdout; use anyhow::Context; use codex_mcp_server::CodexToolCallParam; +use codex_terminal_detection::user_agent; use pretty_assertions::assert_eq; use rmcp::model::CallToolRequestParams; @@ -156,7 +157,7 @@ impl McpProcess { os_info.os_type(), os_info.version(), os_info.architecture().unwrap_or("unknown"), - codex_core::terminal::user_agent() + user_agent() ); let JsonRpcMessage::Response(JsonRpcResponse { jsonrpc, diff --git a/codex-rs/terminal-detection/BUILD.bazel b/codex-rs/terminal-detection/BUILD.bazel new file mode 100644 index 0000000000..a41a762c18 --- /dev/null +++ b/codex-rs/terminal-detection/BUILD.bazel @@ -0,0 +1,6 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "terminal-detection", + crate_name = "codex_terminal_detection", +) diff --git a/codex-rs/terminal-detection/Cargo.toml b/codex-rs/terminal-detection/Cargo.toml new file mode 100644 index 0000000000..f75e649d36 --- /dev/null +++ b/codex-rs/terminal-detection/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "codex-terminal-detection" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +name = "codex_terminal_detection" +path = "src/lib.rs" + +[lints] +workspace = true + +[dependencies] +tracing = { workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } diff --git a/codex-rs/core/src/terminal.rs b/codex-rs/terminal-detection/src/lib.rs similarity index 100% rename from codex-rs/core/src/terminal.rs rename to codex-rs/terminal-detection/src/lib.rs diff --git a/codex-rs/core/src/terminal_tests.rs b/codex-rs/terminal-detection/src/terminal_tests.rs similarity index 100% rename from codex-rs/core/src/terminal_tests.rs rename to codex-rs/terminal-detection/src/terminal_tests.rs diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index 03c3a03dd0..d4b6f25f01 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -43,6 +43,7 @@ codex-otel = { workspace = true } codex-protocol = { workspace = true } codex-shell-command = { workspace = true } codex-state = { workspace = true } +codex-terminal-detection = { workspace = true } codex-tui-app-server = { workspace = true } codex-utils-approval-presets = { workspace = true } codex-utils-absolute-path = { workspace = true } diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 09776f0f77..50b663162d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -80,6 +80,7 @@ use codex_protocol::protocol::SessionConfiguredEvent; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SkillErrorInfo; use codex_protocol::protocol::TokenUsage; +use codex_terminal_detection::user_agent; use codex_utils_absolute_path::AbsolutePathBuf; use color_eyre::eyre::Result; use color_eyre::eyre::WrapErr; @@ -2079,7 +2080,7 @@ impl App { auth_mode, codex_core::default_client::originator().value, config.otel.log_user_prompt, - codex_core::terminal::user_agent(), + user_agent(), SessionSource::Cli, ); if config diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index a1736171cf..897ec389ec 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -76,8 +76,6 @@ use codex_core::models_manager::manager::ModelsManager; use codex_core::plugins::PluginsManager; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::skills::model::SkillMetadata; -use codex_core::terminal::TerminalName; -use codex_core::terminal::terminal_info; #[cfg(target_os = "windows")] use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_otel::RuntimeMetricsSummary; @@ -155,6 +153,8 @@ use codex_protocol::request_permissions::RequestPermissionsEvent; use codex_protocol::request_user_input::RequestUserInputEvent; use codex_protocol::user_input::TextElement; use codex_protocol::user_input::UserInput; +use codex_terminal_detection::TerminalName; +use codex_terminal_detection::terminal_info; use codex_utils_sleep_inhibitor::SleepInhibitor; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 05adf3d307..97cdf7eeef 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -38,7 +38,6 @@ use codex_core::features::Feature; use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::models_manager::manager::ModelsManager; use codex_core::skills::model::SkillMetadata; -use codex_core::terminal::TerminalName; use codex_otel::RuntimeMetricsSummary; use codex_otel::SessionTelemetry; use codex_protocol::ThreadId; @@ -121,6 +120,7 @@ use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_protocol::user_input::TextElement; use codex_protocol::user_input::UserInput; +use codex_terminal_detection::TerminalName; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_approval_presets::builtin_approval_presets; use crossterm::event::KeyCode; diff --git a/codex-rs/tui/src/diff_render.rs b/codex-rs/tui/src/diff_render.rs index dd39901651..684d76b1e0 100644 --- a/codex-rs/tui/src/diff_render.rs +++ b/codex-rs/tui/src/diff_render.rs @@ -93,9 +93,9 @@ use crate::terminal_palette::indexed_color; use crate::terminal_palette::rgb_color; use crate::terminal_palette::stdout_color_level; use codex_core::git_info::get_git_repo_root; -use codex_core::terminal::TerminalName; -use codex_core::terminal::terminal_info; use codex_protocol::protocol::FileChange; +use codex_terminal_detection::TerminalName; +use codex_terminal_detection::terminal_info; /// Classifies a diff line for gutter sign rendering and style selection. /// diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index b4841a7797..8f015981c0 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -33,7 +33,6 @@ use codex_core::format_exec_policy_error_with_source; use codex_core::path_utils; use codex_core::read_session_meta_line; use codex_core::state_db::get_state_db; -use codex_core::terminal::Multiplexer; use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_protocol::ThreadId; use codex_protocol::config_types::AltScreenMode; @@ -43,6 +42,8 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; use codex_state::log_db; +use codex_terminal_detection::Multiplexer; +use codex_terminal_detection::terminal_info; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_oss::ensure_oss_provider_ready; use codex_utils_oss::get_default_model_for_oss_provider; @@ -1138,7 +1139,7 @@ fn determine_alt_screen_mode(no_alt_screen: bool, tui_alternate_screen: AltScree AltScreenMode::Always => true, AltScreenMode::Never => false, AltScreenMode::Auto => { - let terminal_info = codex_core::terminal::terminal_info(); + let terminal_info = terminal_info(); !matches!(terminal_info.multiplexer, Some(Multiplexer::Zellij { .. })) } } diff --git a/codex-rs/tui_app_server/Cargo.toml b/codex-rs/tui_app_server/Cargo.toml index 9ab33202c6..4d9b268895 100644 --- a/codex-rs/tui_app_server/Cargo.toml +++ b/codex-rs/tui_app_server/Cargo.toml @@ -48,6 +48,7 @@ codex-otel = { workspace = true } codex-protocol = { workspace = true } codex-shell-command = { workspace = true } codex-state = { workspace = true } +codex-terminal-detection = { workspace = true } codex-utils-approval-presets = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-cli = { workspace = true } diff --git a/codex-rs/tui_app_server/src/app.rs b/codex-rs/tui_app_server/src/app.rs index 8f8092eac6..52b6db48bb 100644 --- a/codex-rs/tui_app_server/src/app.rs +++ b/codex-rs/tui_app_server/src/app.rs @@ -96,6 +96,7 @@ use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SkillErrorInfo; use codex_protocol::protocol::TokenUsage; +use codex_terminal_detection::user_agent; use codex_utils_absolute_path::AbsolutePathBuf; use color_eyre::eyre::Result; use color_eyre::eyre::WrapErr; @@ -2965,7 +2966,7 @@ impl App { auth_mode, codex_core::default_client::originator().value, config.otel.log_user_prompt, - codex_core::terminal::user_agent(), + user_agent(), SessionSource::Cli, ); if config diff --git a/codex-rs/tui_app_server/src/chatwidget.rs b/codex-rs/tui_app_server/src/chatwidget.rs index d76751c146..eaf9ca1159 100644 --- a/codex-rs/tui_app_server/src/chatwidget.rs +++ b/codex-rs/tui_app_server/src/chatwidget.rs @@ -94,8 +94,6 @@ use codex_core::git_info::local_git_branches; use codex_core::plugins::PluginsManager; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::skills::model::SkillMetadata; -use codex_core::terminal::TerminalName; -use codex_core::terminal::terminal_info; #[cfg(target_os = "windows")] use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_otel::RuntimeMetricsSummary; @@ -199,6 +197,8 @@ use codex_protocol::request_user_input::RequestUserInputEvent; use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_protocol::user_input::TextElement; use codex_protocol::user_input::UserInput; +use codex_terminal_detection::TerminalName; +use codex_terminal_detection::terminal_info; use codex_utils_sleep_inhibitor::SleepInhibitor; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; diff --git a/codex-rs/tui_app_server/src/chatwidget/tests.rs b/codex-rs/tui_app_server/src/chatwidget/tests.rs index bae556d51a..a08d56da8f 100644 --- a/codex-rs/tui_app_server/src/chatwidget/tests.rs +++ b/codex-rs/tui_app_server/src/chatwidget/tests.rs @@ -61,7 +61,6 @@ use codex_core::features::FEATURES; use codex_core::features::Feature; use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::skills::model::SkillMetadata; -use codex_core::terminal::TerminalName; use codex_otel::RuntimeMetricsSummary; use codex_otel::SessionTelemetry; use codex_protocol::ThreadId; @@ -144,6 +143,7 @@ use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_protocol::user_input::TextElement; use codex_protocol::user_input::UserInput; +use codex_terminal_detection::TerminalName; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_approval_presets::builtin_approval_presets; use crossterm::event::KeyCode; diff --git a/codex-rs/tui_app_server/src/diff_render.rs b/codex-rs/tui_app_server/src/diff_render.rs index dd39901651..684d76b1e0 100644 --- a/codex-rs/tui_app_server/src/diff_render.rs +++ b/codex-rs/tui_app_server/src/diff_render.rs @@ -93,9 +93,9 @@ use crate::terminal_palette::indexed_color; use crate::terminal_palette::rgb_color; use crate::terminal_palette::stdout_color_level; use codex_core::git_info::get_git_repo_root; -use codex_core::terminal::TerminalName; -use codex_core::terminal::terminal_info; use codex_protocol::protocol::FileChange; +use codex_terminal_detection::TerminalName; +use codex_terminal_detection::terminal_info; /// Classifies a diff line for gutter sign rendering and style selection. /// diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 19cf079f54..5677806576 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -38,7 +38,6 @@ use codex_core::format_exec_policy_error_with_source; use codex_core::path_utils; use codex_core::read_session_meta_line; use codex_core::state_db::get_state_db; -use codex_core::terminal::Multiplexer; use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_protocol::ThreadId; use codex_protocol::config_types::AltScreenMode; @@ -49,6 +48,8 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::TurnContextItem; use codex_state::log_db; +use codex_terminal_detection::Multiplexer; +use codex_terminal_detection::terminal_info; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_oss::ensure_oss_provider_ready; use codex_utils_oss::get_default_model_for_oss_provider; @@ -1485,7 +1486,7 @@ fn determine_alt_screen_mode(no_alt_screen: bool, tui_alternate_screen: AltScree AltScreenMode::Always => true, AltScreenMode::Never => false, AltScreenMode::Auto => { - let terminal_info = codex_core::terminal::terminal_info(); + let terminal_info = terminal_info(); !matches!(terminal_info.multiplexer, Some(Multiplexer::Zellij { .. })) } }