mirror of
https://github.com/openai/codex.git
synced 2026-04-30 03:12:20 +03:00
Merge origin/main into bug-sweep-report
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// alternate‑screen mode starts; that file opts‑out locally via `allow`.
|
||||
#![deny(clippy::print_stdout, clippy::print_stderr)]
|
||||
#![deny(clippy::disallowed_methods)]
|
||||
use additional_dirs::add_dir_warning_message;
|
||||
use app::App;
|
||||
pub use app::AppExitInfo;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
@@ -11,14 +12,12 @@ use codex_core::BUILT_IN_OSS_MODEL_PROVIDER_ID;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::INTERACTIVE_SESSION_SOURCES;
|
||||
use codex_core::RolloutRecorder;
|
||||
use codex_core::auth::enforce_login_restrictions;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::ConfigToml;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_config_as_toml_with_cli_overrides;
|
||||
use codex_core::find_conversation_path_by_id_str;
|
||||
use codex_core::get_platform_sandbox;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_ollama::DEFAULT_OSS_MODEL;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
|
||||
@@ -27,8 +26,10 @@ use std::path::PathBuf;
|
||||
use tracing::error;
|
||||
use tracing_appender::non_blocking;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::filter::Targets;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
mod additional_dirs;
|
||||
mod app;
|
||||
mod app_backtrack;
|
||||
mod app_event;
|
||||
@@ -36,7 +37,6 @@ mod app_event_sender;
|
||||
mod ascii_animation;
|
||||
mod bottom_pane;
|
||||
mod chatwidget;
|
||||
mod citation_regex;
|
||||
mod cli;
|
||||
mod clipboard_paste;
|
||||
mod color;
|
||||
@@ -63,6 +63,7 @@ mod resume_picker;
|
||||
mod security_prompts;
|
||||
mod security_report_viewer;
|
||||
mod security_review;
|
||||
mod selection_list;
|
||||
mod session_log;
|
||||
mod shimmer;
|
||||
mod slash_command;
|
||||
@@ -74,15 +75,16 @@ mod terminal_palette;
|
||||
mod text_formatting;
|
||||
mod tui;
|
||||
mod ui_consts;
|
||||
pub mod update_action;
|
||||
mod update_prompt;
|
||||
mod updates;
|
||||
mod version;
|
||||
|
||||
mod wrapping;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_backend;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
mod updates;
|
||||
|
||||
use crate::onboarding::TrustDirectorySelection;
|
||||
use crate::onboarding::WSL_INSTRUCTIONS;
|
||||
use crate::onboarding::onboarding_screen::OnboardingScreenArgs;
|
||||
@@ -97,7 +99,7 @@ use std::io::Write as _;
|
||||
// (tests access modules directly within the crate)
|
||||
|
||||
pub async fn run_main(
|
||||
cli: Cli,
|
||||
mut cli: Cli,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
) -> std::io::Result<AppExitInfo> {
|
||||
let (sandbox_mode, approval_policy) = if cli.full_auto {
|
||||
@@ -117,6 +119,13 @@ pub async fn run_main(
|
||||
)
|
||||
};
|
||||
|
||||
// Map the legacy --search flag to the new feature toggle.
|
||||
if cli.web_search {
|
||||
cli.config_overrides
|
||||
.raw_overrides
|
||||
.push("features.web_search_request=true".to_string());
|
||||
}
|
||||
|
||||
// When using `--oss`, let the bootstrapper pick the model (defaulting to
|
||||
// gpt-oss:20b) and ensure it is present locally. Also, force the built‑in
|
||||
// `oss` model provider.
|
||||
@@ -136,6 +145,7 @@ pub async fn run_main(
|
||||
|
||||
// canonicalize the cwd
|
||||
let cwd = cli.cwd.clone().map(|p| p.canonicalize().unwrap_or(p));
|
||||
let additional_dirs = cli.add_dir.clone();
|
||||
|
||||
let overrides = ConfigOverrides {
|
||||
model,
|
||||
@@ -147,11 +157,13 @@ pub async fn run_main(
|
||||
config_profile: cli.config_profile.clone(),
|
||||
codex_linux_sandbox_exe,
|
||||
base_instructions: None,
|
||||
include_plan_tool: Some(true),
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
include_apply_patch_tool: None,
|
||||
include_view_image_tool: None,
|
||||
show_raw_agent_reasoning: cli.oss.then_some(true),
|
||||
tools_web_search_request: cli.web_search.then_some(true),
|
||||
tools_web_search_request: None,
|
||||
experimental_sandbox_command_assessment: None,
|
||||
additional_writable_roots: additional_dirs,
|
||||
};
|
||||
let raw_overrides = cli.config_overrides.raw_overrides.clone();
|
||||
let overrides_cli = codex_common::CliConfigOverrides { raw_overrides };
|
||||
@@ -164,52 +176,23 @@ pub async fn run_main(
|
||||
}
|
||||
};
|
||||
|
||||
let mut config = {
|
||||
// Load configuration and support CLI overrides.
|
||||
let config = load_config_or_exit(cli_kv_overrides.clone(), overrides.clone()).await;
|
||||
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, &config.sandbox_policy) {
|
||||
#[allow(clippy::print_stderr)]
|
||||
match Config::load_with_cli_overrides(cli_kv_overrides.clone(), overrides).await {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
eprintln!("Error loading configuration: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
{
|
||||
eprintln!("Error adding directories: {warning}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// we load config.toml here to determine project state.
|
||||
#[allow(clippy::print_stderr)]
|
||||
let config_toml = {
|
||||
let codex_home = match find_codex_home() {
|
||||
Ok(codex_home) => codex_home,
|
||||
Err(err) => {
|
||||
eprintln!("Error finding codex home: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match load_config_as_toml_with_cli_overrides(&codex_home, cli_kv_overrides).await {
|
||||
Ok(config_toml) => config_toml,
|
||||
Err(err) => {
|
||||
eprintln!("Error loading config.toml: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let cli_profile_override = cli.config_profile.clone();
|
||||
let active_profile = cli_profile_override
|
||||
.clone()
|
||||
.or_else(|| config_toml.profile.clone());
|
||||
|
||||
let should_show_trust_screen = determine_repo_trust_state(
|
||||
&mut config,
|
||||
&config_toml,
|
||||
approval_policy,
|
||||
sandbox_mode,
|
||||
cli_profile_override,
|
||||
)?;
|
||||
if let Err(err) = enforce_login_restrictions(&config).await {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let active_profile = config.active_profile.clone();
|
||||
let log_dir = codex_core::config::log_dir(&config)?;
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
// Open (or create) your log file, appending to it.
|
||||
@@ -238,13 +221,21 @@ pub async fn run_main(
|
||||
})
|
||||
};
|
||||
|
||||
// Build layered subscriber:
|
||||
let file_layer = tracing_subscriber::fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
.with_target(false)
|
||||
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
|
||||
.with_filter(env_filter());
|
||||
|
||||
let feedback = codex_feedback::CodexFeedback::new();
|
||||
let targets = Targets::new().with_default(tracing::Level::TRACE);
|
||||
|
||||
let feedback_layer = tracing_subscriber::fmt::layer()
|
||||
.with_writer(feedback.make_writer())
|
||||
.with_ansi(false)
|
||||
.with_target(false)
|
||||
.with_filter(targets);
|
||||
|
||||
if cli.oss {
|
||||
codex_ollama::ensure_oss_ready(&config)
|
||||
.await
|
||||
@@ -269,24 +260,36 @@ pub async fn run_main(
|
||||
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(file_layer)
|
||||
.with(feedback_layer)
|
||||
.with(otel_layer)
|
||||
.try_init();
|
||||
} else {
|
||||
let _ = tracing_subscriber::registry().with(file_layer).try_init();
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(file_layer)
|
||||
.with(feedback_layer)
|
||||
.try_init();
|
||||
};
|
||||
|
||||
run_ratatui_app(cli, config, active_profile, should_show_trust_screen)
|
||||
.await
|
||||
.map_err(|err| std::io::Error::other(err.to_string()))
|
||||
run_ratatui_app(
|
||||
cli,
|
||||
config,
|
||||
overrides,
|
||||
cli_kv_overrides,
|
||||
active_profile,
|
||||
feedback,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| std::io::Error::other(err.to_string()))
|
||||
}
|
||||
|
||||
async fn run_ratatui_app(
|
||||
cli: Cli,
|
||||
config: Config,
|
||||
initial_config: Config,
|
||||
overrides: ConfigOverrides,
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
active_profile: Option<String>,
|
||||
should_show_trust_screen: bool,
|
||||
feedback: codex_feedback::CodexFeedback,
|
||||
) -> color_eyre::Result<AppExitInfo> {
|
||||
let mut config = config;
|
||||
color_eyre::install()?;
|
||||
|
||||
// Forward panic reports through tracing so they appear in the UI status
|
||||
@@ -303,102 +306,68 @@ async fn run_ratatui_app(
|
||||
|
||||
let mut tui = Tui::new(terminal);
|
||||
|
||||
// Show update banner in terminal history (instead of stderr) so it is visible
|
||||
// within the TUI scrollback. Building spans keeps styling consistent.
|
||||
#[cfg(not(debug_assertions))]
|
||||
if let Some(latest_version) = updates::get_upgrade_version(&config) {
|
||||
use crate::history_cell::padded_emoji;
|
||||
use crate::history_cell::with_border_with_inner_width;
|
||||
use ratatui::style::Stylize as _;
|
||||
use ratatui::text::Line;
|
||||
{
|
||||
use crate::update_prompt::UpdatePromptOutcome;
|
||||
|
||||
let current_version = env!("CARGO_PKG_VERSION");
|
||||
let exe = std::env::current_exe()?;
|
||||
let managed_by_bun = std::env::var_os("CODEX_MANAGED_BY_BUN").is_some();
|
||||
let managed_by_npm = std::env::var_os("CODEX_MANAGED_BY_NPM").is_some();
|
||||
|
||||
let mut content_lines: Vec<Line<'static>> = vec![
|
||||
Line::from(vec![
|
||||
padded_emoji("✨").bold().cyan(),
|
||||
"Update available!".bold().cyan(),
|
||||
" ".into(),
|
||||
format!("{current_version} -> {latest_version}.").bold(),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from("See full release notes:"),
|
||||
Line::from(""),
|
||||
Line::from(
|
||||
"https://github.com/openai/codex/releases/latest"
|
||||
.cyan()
|
||||
.underlined(),
|
||||
),
|
||||
Line::from(""),
|
||||
];
|
||||
|
||||
if managed_by_bun {
|
||||
let bun_cmd = "bun install -g @openai/codex@latest";
|
||||
content_lines.push(Line::from(vec![
|
||||
"Run ".into(),
|
||||
bun_cmd.cyan(),
|
||||
" to update.".into(),
|
||||
]));
|
||||
} else if managed_by_npm {
|
||||
let npm_cmd = "npm install -g @openai/codex@latest";
|
||||
content_lines.push(Line::from(vec![
|
||||
"Run ".into(),
|
||||
npm_cmd.cyan(),
|
||||
" to update.".into(),
|
||||
]));
|
||||
} else if cfg!(target_os = "macos")
|
||||
&& (exe.starts_with("/opt/homebrew") || exe.starts_with("/usr/local"))
|
||||
{
|
||||
let brew_cmd = "brew upgrade codex";
|
||||
content_lines.push(Line::from(vec![
|
||||
"Run ".into(),
|
||||
brew_cmd.cyan(),
|
||||
" to update.".into(),
|
||||
]));
|
||||
} else {
|
||||
content_lines.push(Line::from(vec![
|
||||
"See ".into(),
|
||||
"https://github.com/openai/codex".cyan().underlined(),
|
||||
" for installation options.".into(),
|
||||
]));
|
||||
let skip_update_prompt = cli.prompt.as_ref().is_some_and(|prompt| !prompt.is_empty());
|
||||
if !skip_update_prompt {
|
||||
match update_prompt::run_update_prompt_if_needed(&mut tui, &initial_config).await? {
|
||||
UpdatePromptOutcome::Continue => {}
|
||||
UpdatePromptOutcome::RunUpdate(action) => {
|
||||
crate::tui::restore()?;
|
||||
return Ok(AppExitInfo {
|
||||
token_usage: codex_core::protocol::TokenUsage::default(),
|
||||
conversation_id: None,
|
||||
update_action: Some(action),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let viewport_width = tui.terminal.viewport_area.width as usize;
|
||||
let inner_width = viewport_width.saturating_sub(4).max(1);
|
||||
let mut lines = with_border_with_inner_width(content_lines, inner_width);
|
||||
lines.push("".into());
|
||||
tui.insert_history_lines(lines);
|
||||
}
|
||||
|
||||
// Initialize high-fidelity session event logging if enabled.
|
||||
session_log::maybe_init(&config);
|
||||
session_log::maybe_init(&initial_config);
|
||||
|
||||
let auth_manager = AuthManager::shared(config.codex_home.clone(), false);
|
||||
let login_status = get_login_status(&config);
|
||||
let auth_manager = AuthManager::shared(
|
||||
initial_config.codex_home.clone(),
|
||||
false,
|
||||
initial_config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let login_status = get_login_status(&initial_config);
|
||||
let should_show_trust_screen = should_show_trust_screen(&initial_config);
|
||||
let should_show_windows_wsl_screen =
|
||||
cfg!(target_os = "windows") && !config.windows_wsl_setup_acknowledged;
|
||||
cfg!(target_os = "windows") && !initial_config.windows_wsl_setup_acknowledged;
|
||||
let should_show_onboarding = should_show_onboarding(
|
||||
login_status,
|
||||
&config,
|
||||
&initial_config,
|
||||
should_show_trust_screen,
|
||||
should_show_windows_wsl_screen,
|
||||
);
|
||||
if should_show_onboarding {
|
||||
|
||||
let config = if should_show_onboarding {
|
||||
let onboarding_result = run_onboarding_app(
|
||||
OnboardingScreenArgs {
|
||||
show_login_screen: should_show_login_screen(login_status, &initial_config),
|
||||
show_windows_wsl_screen: should_show_windows_wsl_screen,
|
||||
show_login_screen: should_show_login_screen(login_status, &config),
|
||||
show_trust_screen: should_show_trust_screen,
|
||||
login_status,
|
||||
auth_manager: auth_manager.clone(),
|
||||
config: config.clone(),
|
||||
config: initial_config.clone(),
|
||||
},
|
||||
&mut tui,
|
||||
)
|
||||
.await?;
|
||||
if onboarding_result.should_exit {
|
||||
restore();
|
||||
session_log::log_session_end();
|
||||
let _ = tui.terminal.clear();
|
||||
return Ok(AppExitInfo {
|
||||
token_usage: codex_core::protocol::TokenUsage::default(),
|
||||
conversation_id: None,
|
||||
update_action: None,
|
||||
});
|
||||
}
|
||||
if onboarding_result.windows_install_selected {
|
||||
restore();
|
||||
session_log::log_session_end();
|
||||
@@ -409,16 +378,23 @@ async fn run_ratatui_app(
|
||||
return Ok(AppExitInfo {
|
||||
token_usage: codex_core::protocol::TokenUsage::default(),
|
||||
conversation_id: None,
|
||||
update_action: None,
|
||||
});
|
||||
}
|
||||
if should_show_windows_wsl_screen {
|
||||
config.windows_wsl_setup_acknowledged = true;
|
||||
// if the user acknowledged windows or made an explicit decision ato trust the directory, reload the config accordingly
|
||||
if should_show_windows_wsl_screen
|
||||
|| onboarding_result
|
||||
.directory_trust_decision
|
||||
.map(|d| d == TrustDirectorySelection::Trust)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
load_config_or_exit(cli_kv_overrides, overrides).await
|
||||
} else {
|
||||
initial_config
|
||||
}
|
||||
if let Some(TrustDirectorySelection::Trust) = onboarding_result.directory_trust_decision {
|
||||
config.approval_policy = AskForApproval::OnRequest;
|
||||
config.sandbox_policy = SandboxPolicy::new_workspace_write_policy();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
initial_config
|
||||
};
|
||||
|
||||
// Determine resume behavior: explicit id, then resume last, then picker.
|
||||
let resume_selection = if let Some(id_str) = cli.resume_session_id.as_deref() {
|
||||
@@ -426,15 +402,31 @@ async fn run_ratatui_app(
|
||||
Some(path) => resume_picker::ResumeSelection::Resume(path),
|
||||
None => {
|
||||
error!("Error finding conversation path: {id_str}");
|
||||
resume_picker::ResumeSelection::StartFresh
|
||||
restore();
|
||||
session_log::log_session_end();
|
||||
let _ = tui.terminal.clear();
|
||||
if let Err(err) = writeln!(
|
||||
std::io::stdout(),
|
||||
"No saved session found with ID {id_str}. Run `codex resume` without an ID to choose from existing sessions."
|
||||
) {
|
||||
error!("Failed to write resume error message: {err}");
|
||||
}
|
||||
return Ok(AppExitInfo {
|
||||
token_usage: codex_core::protocol::TokenUsage::default(),
|
||||
conversation_id: None,
|
||||
update_action: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if cli.resume_last {
|
||||
let provider_filter = vec![config.model_provider_id.clone()];
|
||||
match RolloutRecorder::list_conversations(
|
||||
&config.codex_home,
|
||||
1,
|
||||
None,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
Some(provider_filter.as_slice()),
|
||||
&config.model_provider_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -446,13 +438,20 @@ async fn run_ratatui_app(
|
||||
Err(_) => resume_picker::ResumeSelection::StartFresh,
|
||||
}
|
||||
} else if cli.resume_picker {
|
||||
match resume_picker::run_resume_picker(&mut tui, &config.codex_home).await? {
|
||||
match resume_picker::run_resume_picker(
|
||||
&mut tui,
|
||||
&config.codex_home,
|
||||
&config.model_provider_id,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
resume_picker::ResumeSelection::Exit => {
|
||||
restore();
|
||||
session_log::log_session_end();
|
||||
return Ok(AppExitInfo {
|
||||
token_usage: codex_core::protocol::TokenUsage::default(),
|
||||
conversation_id: None,
|
||||
update_action: None,
|
||||
});
|
||||
}
|
||||
other => other,
|
||||
@@ -471,6 +470,7 @@ async fn run_ratatui_app(
|
||||
prompt,
|
||||
images,
|
||||
resume_selection,
|
||||
feedback,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -504,7 +504,7 @@ fn get_login_status(config: &Config) -> LoginStatus {
|
||||
// Reading the OpenAI API key is an async operation because it may need
|
||||
// to refresh the token. Block on it.
|
||||
let codex_home = config.codex_home.clone();
|
||||
match CodexAuth::from_codex_home(&codex_home) {
|
||||
match CodexAuth::from_auth_storage(&codex_home, config.cli_auth_credentials_store_mode) {
|
||||
Ok(Some(auth)) => LoginStatus::AuthMode(auth.mode),
|
||||
Ok(None) => LoginStatus::NotAuthenticated,
|
||||
Err(err) => {
|
||||
@@ -517,42 +517,36 @@ fn get_login_status(config: &Config) -> LoginStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if user has configured a sandbox / approval policy,
|
||||
/// or if the current cwd project is trusted, and updates the config
|
||||
/// accordingly.
|
||||
fn determine_repo_trust_state(
|
||||
config: &mut Config,
|
||||
config_toml: &ConfigToml,
|
||||
approval_policy_overide: Option<AskForApproval>,
|
||||
sandbox_mode_override: Option<SandboxMode>,
|
||||
config_profile_override: Option<String>,
|
||||
) -> std::io::Result<bool> {
|
||||
let config_profile = config_toml.get_config_profile(config_profile_override)?;
|
||||
|
||||
if approval_policy_overide.is_some() || sandbox_mode_override.is_some() {
|
||||
// if the user has overridden either approval policy or sandbox mode,
|
||||
// skip the trust flow
|
||||
Ok(false)
|
||||
} else if config_profile.approval_policy.is_some() {
|
||||
// if the user has specified settings in a config profile, skip the trust flow
|
||||
// todo: profile sandbox mode?
|
||||
Ok(false)
|
||||
} else if config_toml.approval_policy.is_some() || config_toml.sandbox_mode.is_some() {
|
||||
// if the user has specified either approval policy or sandbox mode in config.toml
|
||||
// skip the trust flow
|
||||
Ok(false)
|
||||
} else if config_toml.is_cwd_trusted(&config.cwd) {
|
||||
// if the current cwd project is trusted and no config has been set
|
||||
// skip the trust flow and set the approval policy and sandbox mode
|
||||
config.approval_policy = AskForApproval::OnRequest;
|
||||
config.sandbox_policy = SandboxPolicy::new_workspace_write_policy();
|
||||
Ok(false)
|
||||
} else {
|
||||
// if none of the above conditions are met, show the trust screen
|
||||
Ok(true)
|
||||
async fn load_config_or_exit(
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
overrides: ConfigOverrides,
|
||||
) -> Config {
|
||||
#[allow(clippy::print_stderr)]
|
||||
match Config::load_with_cli_overrides(cli_kv_overrides, overrides).await {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
eprintln!("Error loading configuration: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if user has configured a sandbox / approval policy,
|
||||
/// or if the current cwd project is already trusted. If not, we need to
|
||||
/// show the trust screen.
|
||||
fn should_show_trust_screen(config: &Config) -> bool {
|
||||
if cfg!(target_os = "windows") && get_platform_sandbox().is_none() {
|
||||
// If the experimental sandbox is not enabled, Native Windows cannot enforce sandboxed write access without WSL; skip the trust prompt entirely.
|
||||
return false;
|
||||
}
|
||||
if config.did_user_set_custom_approval_policy_or_sandbox_mode {
|
||||
// Respect explicit approval/sandbox overrides made by the user.
|
||||
return false;
|
||||
}
|
||||
// otherwise, skip iff the active project is trusted
|
||||
!config.active_project.is_trusted()
|
||||
}
|
||||
|
||||
fn should_show_onboarding(
|
||||
login_status: LoginStatus,
|
||||
config: &Config,
|
||||
@@ -579,3 +573,69 @@ fn should_show_login_screen(login_status: LoginStatus, config: &Config) -> bool
|
||||
|
||||
login_status == LoginStatus::NotAuthenticated
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::ConfigToml;
|
||||
use codex_core::config::ProjectConfig;
|
||||
use codex_core::set_windows_sandbox_enabled;
|
||||
use serial_test::serial;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn windows_skips_trust_prompt_without_sandbox() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let mut config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides::default(),
|
||||
temp_dir.path().to_path_buf(),
|
||||
)?;
|
||||
config.did_user_set_custom_approval_policy_or_sandbox_mode = false;
|
||||
config.active_project = ProjectConfig { trust_level: None };
|
||||
set_windows_sandbox_enabled(false);
|
||||
|
||||
let should_show = should_show_trust_screen(&config);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
!should_show,
|
||||
"Windows trust prompt should always be skipped on native Windows"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
should_show,
|
||||
"Non-Windows should still show trust prompt when project is untrusted"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
#[serial]
|
||||
fn windows_shows_trust_prompt_with_sandbox() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let mut config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides::default(),
|
||||
temp_dir.path().to_path_buf(),
|
||||
)?;
|
||||
config.did_user_set_custom_approval_policy_or_sandbox_mode = false;
|
||||
config.active_project = ProjectConfig { trust_level: None };
|
||||
set_windows_sandbox_enabled(true);
|
||||
|
||||
let should_show = should_show_trust_screen(&config);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
should_show,
|
||||
"Windows trust prompt should be shown on native Windows with sandbox enabled"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
should_show,
|
||||
"Non-Windows should still show trust prompt when project is untrusted"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user