mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: add layered --profile-v2 config files (#17141)
## Why `--profile-v2 <name>` gives launchers and runtime entry points a named profile config without making each profile duplicate the base user config. The base `$CODEX_HOME/config.toml` still loads first, then `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active writable user config for that session. That keeps shared defaults, plugin/MCP setup, and managed/user constraints in one place while letting a named profile override only the pieces that need to differ. ## What Changed - Added the shared `--profile-v2 <name>` runtime option with validated plain names, now represented by `ProfileV2Name`. - Extended config layer state so the base user config and selected profile config are both `User` layers; APIs expose the active user layer and merged effective user config. - Threaded profile selection through runtime entry points: `codex`, `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex debug prompt-input`. - Made user-facing config writes go to the selected profile file when active, including TUI/settings persistence, app-server config writes, and MCP/app tool approval persistence. - Made plugin, marketplace, MCP, hooks, and config reload paths read from the merged user config so base and profile layers both participate. - Updated app-server config layer schemas to mark profile-backed user layers. ## Limits `--profile-v2` is still rejected for config-management subcommands such as feature, MCP, and marketplace edits. Those paths remain tied to the base `config.toml` until they have explicit profile-selection semantics. Some adjacent background writes may still update base or global state rather than the selected profile: - marketplace auto-upgrade metadata - automatic MCP dependency installs from skills - remote plugin sync or uninstall config edits - personality migration marker/default writes ## Verification Added targeted coverage for profile name validation, layer ordering/merging, selected-profile writes, app-server config writes, session hot reload, plugin config merging, hooks/config fixture updates, and MCP/app approval persistence. --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -125,6 +125,7 @@ use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnError as AppServerTurnError;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
use codex_config::ConfigLayerStackOrdering;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::types::ApprovalsReviewer;
|
||||
use codex_config::types::ModelAvailabilityNuxConfig;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
@@ -465,6 +466,7 @@ pub(crate) struct App {
|
||||
pub(crate) active_profile: Option<String>,
|
||||
cli_kv_overrides: Vec<(String, TomlValue)>,
|
||||
harness_overrides: ConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
runtime_approval_policy_override: Option<AskForApproval>,
|
||||
runtime_permission_profile_override: Option<PermissionProfile>,
|
||||
|
||||
@@ -629,6 +631,7 @@ impl App {
|
||||
mut config: Config,
|
||||
cli_kv_overrides: Vec<(String, TomlValue)>,
|
||||
harness_overrides: ConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
active_profile: Option<String>,
|
||||
initial_prompt: Option<String>,
|
||||
initial_images: Vec<PathBuf>,
|
||||
@@ -900,6 +903,7 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
active_profile,
|
||||
cli_kv_overrides,
|
||||
harness_overrides,
|
||||
loader_overrides,
|
||||
runtime_approval_policy_override: None,
|
||||
runtime_permission_profile_override: None,
|
||||
file_search,
|
||||
|
||||
@@ -15,6 +15,7 @@ impl App {
|
||||
.codex_home(self.config.codex_home.to_path_buf())
|
||||
.cli_overrides(self.cli_kv_overrides.clone())
|
||||
.harness_overrides(overrides)
|
||||
.loader_overrides(self.loader_overrides.clone())
|
||||
.build()
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to rebuild config for cwd {cwd_display}"))
|
||||
@@ -170,7 +171,7 @@ impl App {
|
||||
(root_blocks_disable, profile_configured)
|
||||
};
|
||||
let mut permissions_history_label: Option<&'static str> = None;
|
||||
let mut builder = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
let mut builder = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(self.active_profile.as_deref());
|
||||
|
||||
for (feature, enabled) in updates {
|
||||
@@ -407,7 +408,7 @@ impl App {
|
||||
},
|
||||
];
|
||||
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits(edits)
|
||||
.apply()
|
||||
.await
|
||||
@@ -591,7 +592,7 @@ mod tests {
|
||||
|
||||
assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None);
|
||||
|
||||
ConfigEditsBuilder::new(&app.config.codex_home)
|
||||
ConfigEditsBuilder::for_config(&app.config)
|
||||
.with_edits([
|
||||
ConfigEdit::SetPath {
|
||||
segments: vec!["apps".to_string(), app_id.clone(), "enabled".to_string()],
|
||||
|
||||
@@ -1079,7 +1079,7 @@ impl App {
|
||||
}
|
||||
let profile = self.active_profile.as_deref();
|
||||
let elevated_enabled = matches!(mode, WindowsSandboxEnableMode::Elevated);
|
||||
let builder = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
let builder = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_windows_sandbox_mode(if elevated_enabled {
|
||||
"elevated"
|
||||
@@ -1182,7 +1182,7 @@ impl App {
|
||||
}
|
||||
AppEvent::PersistModelSelection { model, effort } => {
|
||||
let profile = self.active_profile.as_deref();
|
||||
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_model(Some(model.as_str()), effort)
|
||||
.apply()
|
||||
@@ -1260,7 +1260,7 @@ impl App {
|
||||
}
|
||||
AppEvent::PersistPersonalitySelection { personality } => {
|
||||
let profile = self.active_profile.as_deref();
|
||||
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_personality(Some(personality))
|
||||
.apply()
|
||||
@@ -1297,7 +1297,7 @@ impl App {
|
||||
self.refresh_status_line();
|
||||
let profile = self.active_profile.as_deref();
|
||||
self.config.service_tier = service_tier.clone();
|
||||
let mut edits = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
let mut edits = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_service_tier(service_tier.clone());
|
||||
if service_tier.is_none() {
|
||||
@@ -1335,11 +1335,11 @@ impl App {
|
||||
AppEvent::PersistRealtimeAudioDeviceSelection { kind, name } => {
|
||||
let builder = match kind {
|
||||
RealtimeAudioDeviceKind::Microphone => {
|
||||
ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
ConfigEditsBuilder::for_config(&self.config)
|
||||
.set_realtime_microphone(name.as_deref())
|
||||
}
|
||||
RealtimeAudioDeviceKind::Speaker => {
|
||||
ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
ConfigEditsBuilder::for_config(&self.config)
|
||||
.set_realtime_speaker(name.as_deref())
|
||||
}
|
||||
};
|
||||
@@ -1476,7 +1476,7 @@ impl App {
|
||||
} else {
|
||||
vec!["approvals_reviewer".to_string()]
|
||||
};
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.with_edits([ConfigEdit::SetPath {
|
||||
segments,
|
||||
@@ -1528,7 +1528,7 @@ impl App {
|
||||
self.chat_widget.set_plan_mode_reasoning_effort(effort);
|
||||
}
|
||||
AppEvent::PersistFullAccessWarningAcknowledged => {
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.set_hide_full_access_warning(/*acknowledged*/ true)
|
||||
.apply()
|
||||
.await
|
||||
@@ -1543,7 +1543,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
AppEvent::PersistWorldWritableWarningAcknowledged => {
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.set_hide_world_writable_warning(/*acknowledged*/ true)
|
||||
.apply()
|
||||
.await
|
||||
@@ -1558,7 +1558,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
AppEvent::PersistRateLimitSwitchPromptHidden => {
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.set_hide_rate_limit_model_nudge(/*acknowledged*/ true)
|
||||
.apply()
|
||||
.await
|
||||
@@ -1591,7 +1591,7 @@ impl App {
|
||||
} else {
|
||||
ConfigEdit::ClearPath { segments }
|
||||
};
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([edit])
|
||||
.apply()
|
||||
.await
|
||||
@@ -1615,7 +1615,7 @@ impl App {
|
||||
from_model,
|
||||
to_model,
|
||||
} => {
|
||||
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.record_model_migration_seen(from_model.as_str(), to_model.as_str())
|
||||
.apply()
|
||||
.await
|
||||
@@ -1658,7 +1658,7 @@ impl App {
|
||||
path: path.to_path_buf(),
|
||||
enabled,
|
||||
}];
|
||||
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits(edits)
|
||||
.apply()
|
||||
.await
|
||||
@@ -1710,7 +1710,7 @@ impl App {
|
||||
},
|
||||
]
|
||||
};
|
||||
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits(edits)
|
||||
.apply()
|
||||
.await
|
||||
@@ -1873,7 +1873,7 @@ impl App {
|
||||
let items_edit = crate::legacy_core::config::edit::status_line_items_edit(&ids);
|
||||
let colors_edit =
|
||||
crate::legacy_core::config::edit::status_line_use_colors_edit(use_theme_colors);
|
||||
let apply_result = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
let apply_result = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([items_edit, colors_edit])
|
||||
.apply()
|
||||
.await;
|
||||
@@ -1905,7 +1905,7 @@ impl App {
|
||||
AppEvent::TerminalTitleSetup { items } => {
|
||||
let ids = items.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
let edit = crate::legacy_core::config::edit::terminal_title_items_edit(&ids);
|
||||
let apply_result = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
let apply_result = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([edit])
|
||||
.apply()
|
||||
.await;
|
||||
@@ -1931,7 +1931,7 @@ impl App {
|
||||
}
|
||||
AppEvent::SyntaxThemeSelected { name } => {
|
||||
let edit = crate::legacy_core::config::edit::syntax_theme_edit(&name);
|
||||
let apply_result = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
let apply_result = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([edit])
|
||||
.apply()
|
||||
.await;
|
||||
@@ -2043,7 +2043,7 @@ impl App {
|
||||
|
||||
let edit =
|
||||
crate::legacy_core::config::edit::keymap_bindings_edit(&context, &action, &bindings);
|
||||
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([edit])
|
||||
.apply()
|
||||
.await
|
||||
@@ -2088,7 +2088,7 @@ impl App {
|
||||
};
|
||||
|
||||
let edit = crate::legacy_core::config::edit::keymap_binding_clear_edit(&context, &action);
|
||||
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([edit])
|
||||
.apply()
|
||||
.await
|
||||
|
||||
@@ -209,7 +209,7 @@ pub(super) async fn prepare_startup_tooltip_override(
|
||||
let mut updated_shown_count = config.model_availability_nux.shown_count.clone();
|
||||
updated_shown_count.insert(tooltip_override.model_slug.clone(), next_count);
|
||||
|
||||
if let Err(err) = ConfigEditsBuilder::new(&config.codex_home)
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(config)
|
||||
.set_model_availability_nux_count(&updated_shown_count)
|
||||
.apply()
|
||||
.await
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(super) async fn make_test_app() -> App {
|
||||
active_profile: None,
|
||||
cli_kv_overrides: Vec::new(),
|
||||
harness_overrides: ConfigOverrides::default(),
|
||||
loader_overrides: LoaderOverrides::without_managed_config_for_tests(),
|
||||
runtime_approval_policy_override: None,
|
||||
runtime_permission_profile_override: None,
|
||||
file_search,
|
||||
|
||||
@@ -3489,35 +3489,38 @@ async fn discard_side_thread_removes_agent_navigation_entry() -> Result<()> {
|
||||
|
||||
#[tokio::test]
|
||||
async fn discard_side_thread_keeps_local_state_when_server_close_fails() -> Result<()> {
|
||||
let mut app = make_test_app().await;
|
||||
let mut app_server =
|
||||
crate::start_embedded_app_server_for_picker(app.chat_widget.config_ref()).await?;
|
||||
let parent_thread_id = ThreadId::new();
|
||||
let side_thread_id = ThreadId::new();
|
||||
app.active_thread_id = Some(side_thread_id);
|
||||
app.side_threads
|
||||
.insert(side_thread_id, SideThreadState::new(parent_thread_id));
|
||||
app.agent_navigation.upsert(
|
||||
side_thread_id,
|
||||
Some("Side".to_string()),
|
||||
Some("side".to_string()),
|
||||
/*is_closed*/ false,
|
||||
);
|
||||
|
||||
assert!(
|
||||
!app.discard_side_thread(&mut app_server, side_thread_id)
|
||||
.await
|
||||
);
|
||||
|
||||
assert_eq!(app.active_thread_id, Some(side_thread_id));
|
||||
assert_eq!(
|
||||
Box::pin(async {
|
||||
let mut app = make_test_app().await;
|
||||
let mut app_server =
|
||||
crate::start_embedded_app_server_for_picker(app.chat_widget.config_ref()).await?;
|
||||
let parent_thread_id = ThreadId::new();
|
||||
let side_thread_id = ThreadId::new();
|
||||
app.active_thread_id = Some(side_thread_id);
|
||||
app.side_threads
|
||||
.get(&side_thread_id)
|
||||
.map(|state| state.parent_thread_id),
|
||||
Some(parent_thread_id)
|
||||
);
|
||||
assert!(app.agent_navigation.get(&side_thread_id).is_some());
|
||||
Ok(())
|
||||
.insert(side_thread_id, SideThreadState::new(parent_thread_id));
|
||||
app.agent_navigation.upsert(
|
||||
side_thread_id,
|
||||
Some("Side".to_string()),
|
||||
Some("side".to_string()),
|
||||
/*is_closed*/ false,
|
||||
);
|
||||
|
||||
assert!(
|
||||
!app.discard_side_thread(&mut app_server, side_thread_id)
|
||||
.await
|
||||
);
|
||||
|
||||
assert_eq!(app.active_thread_id, Some(side_thread_id));
|
||||
assert_eq!(
|
||||
app.side_threads
|
||||
.get(&side_thread_id)
|
||||
.map(|state| state.parent_thread_id),
|
||||
Some(parent_thread_id)
|
||||
);
|
||||
assert!(app.agent_navigation.get(&side_thread_id).is_some());
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -3810,6 +3813,7 @@ async fn make_test_app() -> App {
|
||||
active_profile: None,
|
||||
cli_kv_overrides: Vec::new(),
|
||||
harness_overrides: ConfigOverrides::default(),
|
||||
loader_overrides: LoaderOverrides::without_managed_config_for_tests(),
|
||||
runtime_approval_policy_override: None,
|
||||
runtime_permission_profile_override: None,
|
||||
file_search,
|
||||
@@ -3872,6 +3876,7 @@ async fn make_test_app_with_channels() -> (
|
||||
active_profile: None,
|
||||
cli_kv_overrides: Vec::new(),
|
||||
harness_overrides: ConfigOverrides::default(),
|
||||
loader_overrides: LoaderOverrides::without_managed_config_for_tests(),
|
||||
runtime_approval_policy_override: None,
|
||||
runtime_permission_profile_override: None,
|
||||
file_search,
|
||||
|
||||
@@ -2023,10 +2023,11 @@ fn marketplace_display_name(marketplace: &PluginMarketplaceEntry) -> String {
|
||||
}
|
||||
|
||||
fn marketplace_is_user_configured(config: &Config, marketplace_name: &str) -> bool {
|
||||
config
|
||||
.config_layer_stack
|
||||
.get_user_layer()
|
||||
.and_then(|user_layer| user_layer.config.get("marketplaces"))
|
||||
let Some(user_config) = config.config_layer_stack.effective_user_config() else {
|
||||
return false;
|
||||
};
|
||||
user_config
|
||||
.get("marketplaces")
|
||||
.and_then(toml::Value::as_table)
|
||||
.is_some_and(|marketplaces| marketplaces.contains_key(marketplace_name))
|
||||
}
|
||||
@@ -2034,7 +2035,7 @@ fn marketplace_is_user_configured(config: &Config, marketplace_name: &str) -> bo
|
||||
fn marketplace_is_user_configured_git(config: &Config, marketplace_name: &str) -> bool {
|
||||
config
|
||||
.config_layer_stack
|
||||
.get_user_layer()
|
||||
.get_active_user_layer()
|
||||
.and_then(|user_layer| user_layer.config.get("marketplaces"))
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|marketplaces| marketplaces.get(marketplace_name))
|
||||
|
||||
@@ -385,7 +385,7 @@ fn format_config_layer_source(source: &ConfigLayerSource) -> String {
|
||||
ConfigLayerSource::System { file } => {
|
||||
format!("system ({})", file.as_path().display())
|
||||
}
|
||||
ConfigLayerSource::User { file } => {
|
||||
ConfigLayerSource::User { file, .. } => {
|
||||
format!("user ({})", file.as_path().display())
|
||||
}
|
||||
ConfigLayerSource::Project { dot_codex_folder } => {
|
||||
@@ -728,7 +728,10 @@ mod tests {
|
||||
};
|
||||
let stack = ConfigLayerStack::new(
|
||||
vec![ConfigLayerEntry::new(
|
||||
ConfigLayerSource::User { file: user_file },
|
||||
ConfigLayerSource::User {
|
||||
file: user_file,
|
||||
profile: None,
|
||||
},
|
||||
empty_toml_table(),
|
||||
)],
|
||||
requirements,
|
||||
|
||||
@@ -148,7 +148,7 @@ async fn persist_external_agent_config_migration_prompt_shown(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ConfigEditsBuilder::new(&config.codex_home)
|
||||
ConfigEditsBuilder::for_config(config)
|
||||
.with_edits(edits)
|
||||
.apply()
|
||||
.await
|
||||
@@ -221,7 +221,7 @@ async fn persist_external_agent_config_migration_prompt_dismissal(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ConfigEditsBuilder::new(&config.codex_home)
|
||||
ConfigEditsBuilder::for_config(config)
|
||||
.with_edits(edits)
|
||||
.apply()
|
||||
.await
|
||||
|
||||
+17
-9
@@ -10,6 +10,7 @@ use crate::legacy_core::config::ConfigOverrides;
|
||||
use crate::legacy_core::config::find_codex_home;
|
||||
use crate::legacy_core::config::load_config_as_toml_with_cli_and_load_options;
|
||||
use crate::legacy_core::config::resolve_oss_provider;
|
||||
use crate::legacy_core::config::resolve_profile_v2_config_path;
|
||||
use crate::legacy_core::format_exec_policy_error_with_source;
|
||||
use crate::legacy_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use crate::session_resume::ResolveCwdOutcome;
|
||||
@@ -842,6 +843,12 @@ pub async fn run_main(
|
||||
let cwd = cli.cwd.clone();
|
||||
let config_cwd =
|
||||
config_cwd_for_app_server_target(cwd.as_deref(), &app_server_target, &environment_manager)?;
|
||||
let mut loader_overrides = loader_overrides;
|
||||
if let Some(profile_v2) = cli.config_profile_v2.as_ref() {
|
||||
let user_config_path = resolve_profile_v2_config_path(&codex_home, profile_v2);
|
||||
loader_overrides.user_config_path = Some(user_config_path);
|
||||
loader_overrides.user_config_profile = Some(profile_v2.clone());
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
let config_toml = match load_config_as_toml_with_cli_and_load_options(
|
||||
@@ -946,8 +953,8 @@ pub async fn run_main(
|
||||
let mut config = load_config_or_exit(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await;
|
||||
@@ -1003,8 +1010,8 @@ pub async fn run_main(
|
||||
config = load_config_or_exit(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await;
|
||||
@@ -1321,8 +1328,8 @@ async fn run_ratatui_app(
|
||||
load_config_or_exit(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await
|
||||
@@ -1525,8 +1532,8 @@ async fn run_ratatui_app(
|
||||
load_config_or_exit_with_fallback_cwd(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
strict_config,
|
||||
fallback_cwd,
|
||||
)
|
||||
@@ -1536,8 +1543,8 @@ async fn run_ratatui_app(
|
||||
load_config_or_exit(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await
|
||||
@@ -1579,7 +1586,7 @@ async fn run_ratatui_app(
|
||||
arg0_paths,
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides,
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
@@ -1611,6 +1618,7 @@ async fn run_ratatui_app(
|
||||
config,
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
active_profile,
|
||||
prompt,
|
||||
images,
|
||||
@@ -1721,15 +1729,15 @@ async fn get_login_status(
|
||||
async fn load_config_or_exit(
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
overrides: ConfigOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
loader_overrides: LoaderOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
strict_config: bool,
|
||||
) -> Config {
|
||||
load_config_or_exit_with_fallback_cwd(
|
||||
cli_kv_overrides,
|
||||
overrides,
|
||||
cloud_requirements,
|
||||
loader_overrides,
|
||||
cloud_requirements,
|
||||
strict_config,
|
||||
/*fallback_cwd*/ None,
|
||||
)
|
||||
@@ -1739,8 +1747,8 @@ async fn load_config_or_exit(
|
||||
async fn load_config_or_exit_with_fallback_cwd(
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
overrides: ConfigOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
loader_overrides: LoaderOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
strict_config: bool,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
) -> Config {
|
||||
|
||||
@@ -24,6 +24,7 @@ use codex_app_server_protocol::PermissionProfileFileSystemPermissions;
|
||||
use codex_app_server_protocol::PermissionProfileNetworkPermissions;
|
||||
use codex_app_server_protocol::RateLimitSnapshot;
|
||||
use codex_app_server_protocol::RateLimitWindow;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_protocol::ThreadId;
|
||||
@@ -80,6 +81,7 @@ fn app_server_workspace_write_profile(network_enabled: bool) -> PermissionProfil
|
||||
async fn test_config(temp_home: &TempDir) -> Config {
|
||||
let mut config = ConfigBuilder::default()
|
||||
.codex_home(temp_home.path().to_path_buf())
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.build()
|
||||
.await
|
||||
.expect("load config");
|
||||
|
||||
Reference in New Issue
Block a user