add(core): managed config (#3868)

## Summary

- Factor `load_config_as_toml` into `core::config_loader` so config
loading is reusable across callers.
- Layer `~/.codex/config.toml`, optional `~/.codex/managed_config.toml`,
and macOS managed preferences (base64) with recursive table merging and
scoped threads per source.

## Config Flow

```
Managed prefs (macOS profile: com.openai.codex/config_toml_base64)
                               ▲
                               │
~/.codex/managed_config.toml   │  (optional file-based override)
                               ▲
                               │
                ~/.codex/config.toml (user-defined settings)
```

- The loader searches under the resolved `CODEX_HOME` directory
(defaults to `~/.codex`).
- Managed configs let administrators ship fleet-wide overrides via
device profiles which is useful for enforcing certain settings like
sandbox or approval defaults.
- For nested hash tables: overlays merge recursively. Child tables are
merged key-by-key, while scalar or array values replace the prior layer
entirely. This lets admins add or tweak individual fields without
clobbering unrelated user settings.
This commit is contained in:
Fouad Matin
2025-10-03 13:02:26 -07:00
committed by GitHub
parent 9823de3cc6
commit a5b7675e42
21 changed files with 676 additions and 194 deletions

View File

@@ -91,15 +91,14 @@ mod tests {
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
fn test_config() -> Config {
async fn test_config() -> Config {
let overrides = ConfigOverrides {
cwd: std::env::current_dir().ok(),
..Default::default()
};
match Config::load_with_cli_overrides(vec![], overrides) {
Ok(c) => c,
Err(e) => panic!("load test config: {e}"),
}
Config::load_with_cli_overrides(vec![], overrides)
.await
.expect("load test config")
}
fn lines_to_plain_strings(lines: &[ratatui::text::Line<'_>]) -> Vec<String> {
@@ -115,9 +114,9 @@ mod tests {
.collect()
}
#[test]
fn controller_loose_vs_tight_with_commit_ticks_matches_full() {
let cfg = test_config();
#[tokio::test]
async fn controller_loose_vs_tight_with_commit_ticks_matches_full() {
let cfg = test_config().await;
let mut ctrl = StreamController::new(cfg.clone(), None);
let mut lines = Vec::new();