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

@@ -132,7 +132,7 @@ pub(crate) fn create_tool_for_codex_tool_call_param() -> Tool {
impl CodexToolCallParam {
/// Returns the initial user prompt to start the Codex conversation and the
/// effective Config object generated from the supplied parameters.
pub fn into_config(
pub async fn into_config(
self,
codex_linux_sandbox_exe: Option<PathBuf>,
) -> std::io::Result<(String, codex_core::config::Config)> {
@@ -172,7 +172,8 @@ impl CodexToolCallParam {
.map(|(k, v)| (k, json_to_toml(v)))
.collect();
let cfg = codex_core::config::Config::load_with_cli_overrides(cli_overrides, overrides)?;
let cfg =
codex_core::config::Config::load_with_cli_overrides(cli_overrides, overrides).await?;
Ok((prompt, cfg))
}

View File

@@ -91,6 +91,7 @@ pub async fn run_main(
)
})?;
let config = Config::load_with_cli_overrides(cli_kv_overrides, ConfigOverrides::default())
.await
.map_err(|e| {
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
})?;

View File

@@ -342,7 +342,10 @@ impl MessageProcessor {
async fn handle_tool_call_codex(&self, id: RequestId, arguments: Option<serde_json::Value>) {
let (initial_prompt, config): (String, Config) = match arguments {
Some(json_val) => match serde_json::from_value::<CodexToolCallParam>(json_val) {
Ok(tool_cfg) => match tool_cfg.into_config(self.codex_linux_sandbox_exe.clone()) {
Ok(tool_cfg) => match tool_cfg
.into_config(self.codex_linux_sandbox_exe.clone())
.await
{
Ok(cfg) => cfg,
Err(e) => {
let result = CallToolResult {