Skip to content
Merged
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
106 changes: 97 additions & 9 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ use codex_app_server_protocol::UserSavedConfig;
use codex_app_server_protocol::build_turns_from_event_msgs;
use codex_backend_client::Client as BackendClient;
use codex_chatgpt::connectors;
use codex_cloud_requirements::cloud_requirements_loader;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::CodexThread;
Expand All @@ -162,6 +163,7 @@ use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config::types::McpServerTransportConfig;
use codex_core::config_loader::CloudRequirementsLoader;
use codex_core::default_client::get_codex_user_agent;
use codex_core::default_client::set_default_client_residency_requirement;
use codex_core::error::CodexErr;
use codex_core::exec::ExecParams;
use codex_core::exec_env::create_env;
Expand Down Expand Up @@ -218,6 +220,7 @@ use std::io::Error as IoError;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
Expand Down Expand Up @@ -277,7 +280,7 @@ pub(crate) struct CodexMessageProcessor {
codex_linux_sandbox_exe: Option<PathBuf>,
config: Arc<Config>,
cli_overrides: Vec<(String, TomlValue)>,
cloud_requirements: CloudRequirementsLoader,
cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
conversation_listeners: HashMap<Uuid, oneshot::Sender<()>>,
listener_thread_ids_by_subscription: HashMap<Uuid, ThreadId>,
active_login: Arc<Mutex<Option<ActiveLogin>>>,
Expand Down Expand Up @@ -349,7 +352,7 @@ impl CodexMessageProcessor {
codex_linux_sandbox_exe,
config,
cli_overrides,
cloud_requirements,
cloud_requirements: Arc::new(RwLock::new(cloud_requirements)),
conversation_listeners: HashMap::new(),
listener_thread_ids_by_subscription: HashMap::new(),
active_login: Arc::new(Mutex::new(None)),
Expand All @@ -362,9 +365,10 @@ impl CodexMessageProcessor {
}

async fn load_latest_config(&self) -> Result<Config, JSONRPCErrorError> {
let cloud_requirements = self.current_cloud_requirements();
codex_core::config::ConfigBuilder::default()
.cli_overrides(self.cli_overrides.clone())
.cloud_requirements(self.cloud_requirements.clone())
.cloud_requirements(cloud_requirements)
.build()
.await
.map_err(|err| JSONRPCErrorError {
Expand All @@ -374,6 +378,13 @@ impl CodexMessageProcessor {
})
}

fn current_cloud_requirements(&self) -> CloudRequirementsLoader {
self.cloud_requirements
.read()
.map(|guard| guard.clone())
.unwrap_or_default()
}

fn review_request_from_target(
target: ApiReviewTarget,
) -> Result<(ReviewRequest, String), JSONRPCErrorError> {
Expand Down Expand Up @@ -834,6 +845,9 @@ impl CodexMessageProcessor {
let outgoing_clone = self.outgoing.clone();
let active_login = self.active_login.clone();
let auth_manager = self.auth_manager.clone();
let cloud_requirements = self.cloud_requirements.clone();
let chatgpt_base_url = self.config.chatgpt_base_url.clone();
let cli_overrides = self.cli_overrides.clone();
let auth_url = server.auth_url.clone();
tokio::spawn(async move {
let (success, error_msg) = match tokio::time::timeout(
Expand Down Expand Up @@ -863,6 +877,16 @@ impl CodexMessageProcessor {

if success {
auth_manager.reload();
replace_cloud_requirements_loader(
cloud_requirements.as_ref(),
auth_manager.clone(),
chatgpt_base_url,
);
sync_default_client_residency_requirement(
&cli_overrides,
cloud_requirements.as_ref(),
)
.await;

// Notify clients with the actual current auth mode.
let current_auth_method = auth_manager
Expand Down Expand Up @@ -927,6 +951,9 @@ impl CodexMessageProcessor {
let outgoing_clone = self.outgoing.clone();
let active_login = self.active_login.clone();
let auth_manager = self.auth_manager.clone();
let cloud_requirements = self.cloud_requirements.clone();
let chatgpt_base_url = self.config.chatgpt_base_url.clone();
let cli_overrides = self.cli_overrides.clone();
let auth_url = server.auth_url.clone();
tokio::spawn(async move {
let (success, error_msg) = match tokio::time::timeout(
Expand Down Expand Up @@ -956,6 +983,16 @@ impl CodexMessageProcessor {

if success {
auth_manager.reload();
replace_cloud_requirements_loader(
cloud_requirements.as_ref(),
auth_manager.clone(),
chatgpt_base_url,
);
sync_default_client_residency_requirement(
&cli_overrides,
cloud_requirements.as_ref(),
)
.await;

// Notify clients with the actual current auth mode.
let current_auth_method = auth_manager
Expand Down Expand Up @@ -1123,6 +1160,16 @@ impl CodexMessageProcessor {
return;
}
self.auth_manager.reload();
replace_cloud_requirements_loader(
self.cloud_requirements.as_ref(),
self.auth_manager.clone(),
self.config.chatgpt_base_url.clone(),
);
sync_default_client_residency_requirement(
&self.cli_overrides,
self.cloud_requirements.as_ref(),
)
.await;

self.outgoing
.send_response(request_id, LoginAccountResponse::ChatgptAuthTokens {})
Expand Down Expand Up @@ -1561,11 +1608,12 @@ impl CodexMessageProcessor {
);
}

let cloud_requirements = self.current_cloud_requirements();
let config = match derive_config_from_params(
&self.cli_overrides,
Some(request_overrides),
typesafe_overrides,
&self.cloud_requirements,
&cloud_requirements,
)
.await
{
Expand Down Expand Up @@ -1647,11 +1695,12 @@ impl CodexMessageProcessor {
);
typesafe_overrides.ephemeral = ephemeral;

let cloud_requirements = self.current_cloud_requirements();
let config = match derive_config_from_params(
&self.cli_overrides,
config,
typesafe_overrides,
&self.cloud_requirements,
&cloud_requirements,
)
.await
{
Expand Down Expand Up @@ -2440,12 +2489,13 @@ impl CodexMessageProcessor {
);

// Derive a Config using the same logic as new conversation, honoring overrides if provided.
let cloud_requirements = self.current_cloud_requirements();
let config = match derive_config_for_cwd(
&self.cli_overrides,
request_overrides,
typesafe_overrides,
history_cwd,
&self.cloud_requirements,
&cloud_requirements,
)
.await
{
Expand Down Expand Up @@ -2633,12 +2683,13 @@ impl CodexMessageProcessor {
None,
);
// Derive a Config using the same logic as new conversation, honoring overrides if provided.
let cloud_requirements = self.current_cloud_requirements();
let config = match derive_config_for_cwd(
&self.cli_overrides,
request_overrides,
typesafe_overrides,
history_cwd,
&self.cloud_requirements,
&cloud_requirements,
)
.await
{
Expand Down Expand Up @@ -3438,12 +3489,13 @@ impl CodexMessageProcessor {
),
};

let cloud_requirements = self.current_cloud_requirements();
let config = match derive_config_for_cwd(
&self.cli_overrides,
request_overrides,
typesafe_overrides,
history_cwd,
&self.cloud_requirements,
&cloud_requirements,
)
.await
{
Expand Down Expand Up @@ -3627,12 +3679,13 @@ impl CodexMessageProcessor {
),
};

let cloud_requirements = self.current_cloud_requirements();
let config = match derive_config_for_cwd(
&self.cli_overrides,
request_overrides,
typesafe_overrides,
history_cwd,
&self.cloud_requirements,
&cloud_requirements,
)
.await
{
Expand Down Expand Up @@ -4944,6 +4997,41 @@ fn validate_dynamic_tools(
Ok(())
}

fn replace_cloud_requirements_loader(
cloud_requirements: &RwLock<CloudRequirementsLoader>,
auth_manager: Arc<AuthManager>,
chatgpt_base_url: String,
) {
let loader = cloud_requirements_loader(auth_manager, chatgpt_base_url);
if let Ok(mut guard) = cloud_requirements.write() {
*guard = loader;
} else {
warn!("failed to update cloud requirements loader");
}
}

async fn sync_default_client_residency_requirement(
cli_overrides: &[(String, TomlValue)],
cloud_requirements: &RwLock<CloudRequirementsLoader>,
) {
let loader = cloud_requirements
.read()
.map(|guard| guard.clone())
.unwrap_or_default();
match codex_core::config::ConfigBuilder::default()
.cli_overrides(cli_overrides.to_vec())
.cloud_requirements(loader)
.build()
.await
{
Ok(config) => set_default_client_residency_requirement(config.enforce_residency.value()),
Err(err) => warn!(
error = %err,
"failed to sync default client residency requirement after auth refresh"
),
}
}

/// Derive the effective [`Config`] by layering three override sources.
///
/// Precedence (lowest to highest):
Expand Down
22 changes: 17 additions & 5 deletions codex-rs/tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ async fn run_ratatui_app(
initial_config: Config,
overrides: ConfigOverrides,
cli_kv_overrides: Vec<(String, toml::Value)>,
cloud_requirements: CloudRequirementsLoader,
mut cloud_requirements: CloudRequirementsLoader,
feedback: codex_feedback::CodexFeedback,
) -> color_eyre::Result<AppExitInfo> {
color_eyre::install()?;
Expand Down Expand Up @@ -470,9 +470,10 @@ async fn run_ratatui_app(
should_show_onboarding(login_status, &initial_config, should_show_trust_screen_flag);

let config = if should_show_onboarding {
let show_login_screen = should_show_login_screen(login_status, &initial_config);
let onboarding_result = run_onboarding_app(
OnboardingScreenArgs {
show_login_screen: should_show_login_screen(login_status, &initial_config),
show_login_screen,
show_trust_screen: should_show_trust_screen_flag,
login_status,
auth_manager: auth_manager.clone(),
Expand All @@ -493,9 +494,19 @@ async fn run_ratatui_app(
exit_reason: ExitReason::UserRequested,
});
}
// If the user made an explicit trust decision, reload config so current
// process state reflects what was persisted to config.toml.
if onboarding_result.directory_trust_decision.is_some() {
// If this onboarding run included the login step, always refresh cloud requirements and
// rebuild config. This avoids missing newly available cloud requirements due to login
// status detection edge cases.
if show_login_screen {
cloud_requirements = cloud_requirements_loader(
auth_manager.clone(),
initial_config.chatgpt_base_url.clone(),
);
}

// If the user made an explicit trust decision, or we showed the login flow, reload config
// so current process state reflects persisted trust/auth changes.
if onboarding_result.directory_trust_decision.is_some() || show_login_screen {
load_config_or_exit(
cli_kv_overrides.clone(),
overrides.clone(),
Expand Down Expand Up @@ -668,6 +679,7 @@ async fn run_ratatui_app(
}
_ => config,
};
set_default_client_residency_requirement(config.enforce_residency.value());
let active_profile = config.active_profile.clone();
let should_show_trust_screen = should_show_trust_screen(&config);

Expand Down
Loading