Commit Graph

13 Commits

  • Reapply "Add app-server transport layer with websocket support" (#11370)
    Reapply "Add app-server transport layer with websocket support" with
    additional fixes from https://github.com/openai/codex/pull/11313/changes
    to avoid deadlocking.
    
    This reverts commit 47356ff83c.
    
    ## Summary
    
    To avoid deadlocking when queues are full, we maintain separate tokio
    tasks dedicated to incoming vs outgoing event handling
    - split the app-server main loop into two tasks in
    `run_main_with_transport`
       - inbound handling (`transport_event_rx`)
       - outbound handling (`outgoing_rx` + `thread_created_rx`)
    - separate incoming and outgoing websocket tasks
    
    ## Validation
    
    Integration tests, testing thoroughly e2e in codex app w/ >10 concurrent
    requests
    
    <img width="1365" height="979" alt="Screenshot 2026-02-10 at 2 54 22 PM"
    src="https://github.com/user-attachments/assets/47ca2c13-f322-4e5c-bedd-25859cbdc45f"
    />
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Load requirements on windows (#10770)
    We support requirements on Unix, loading from
    `/etc/codex/requirements.toml`. On MacOS, we also support MDM.
    
    Now, on Windows, we'll load requirements from
    `%ProgramData%\OpenAI\Codex\requirements.toml`
  • Add app configs to config.toml (#10822)
    Adds app configs to config.toml + tests
  • Add layered config.toml support to app server (#9510)
    This PR adds support for chained (layered) config.toml file merging for
    clients that use the app server interface. This feature already exists
    for the TUI, but it does not work for GUI clients.
    
    It does the following:
    * Changes code paths for new thread, resume thread, and fork thread to
    use the effective config based on the cwd.
    * Updates the `config/read` API to accept an optional `cwd` parameter.
    If specified, the API returns the effective config based on that cwd
    path. Also optionally includes all layers including project config
    files. If cwd is not specified, the API falls back on its older behavior
    where it considers only the global (non-project) config files when
    computing the effective config.
    
    The changes in codex_message_processor.rs look deceptively large. They
    mostly just involve moving existing blocks of code to a later point in
    some functions so it can use the cwd to calculate the config.
    
    This PR builds upon #9509 and should be reviewed and merged after that
    PR.
    
    Tested:
    * Verified change with (dependent, as-yet-uncommitted) changes to IDE
    Extension and confirmed correct behavior
    
    The full fix requires additional changes in the IDE Extension code base,
    but they depend on this PR.
  • feat: honor /etc/codex/config.toml (#8461)
    This adds logic to load `/etc/codex/config.toml` and associate it with
    `ConfigLayerSource::System` on UNIX. I refactored the code so it shares
    logic with the creation of the `ConfigLayerSource::User` layer.
  • feat: migrate to new constraint-based loading strategy (#8251)
    This is a significant change to how layers of configuration are applied.
    In particular, the `ConfigLayerStack` now has two important fields:
    
    - `layers: Vec<ConfigLayerEntry>`
    - `requirements: ConfigRequirements`
    
    We merge `TomlValue`s across the layers, but they are subject to
    `ConfigRequirements` before creating a `Config`.
    
    How I would review this PR:
    
    - start with `codex-rs/app-server-protocol/src/protocol/v2.rs` and note
    the new variants added to the `ConfigLayerSource` enum:
    `LegacyManagedConfigTomlFromFile` and `LegacyManagedConfigTomlFromMdm`
    - note that `ConfigLayerSource` now has a `precedence()` method and
    implements `PartialOrd`
    - `codex-rs/core/src/config_loader/layer_io.rs` is responsible for
    loading "admin" preferences from `/etc/codex/managed_config.toml` and
    MDM. Because `/etc/codex/managed_config.toml` is now deprecated in favor
    of `/etc/codex/requirements.toml` and `/etc/codex/config.toml`, we now
    include some extra information on the `LoadedConfigLayers` returned in
    `layer_io.rs`.
    - `codex-rs/core/src/config_loader/mod.rs` has major changes to
    `load_config_layers_state()`, which is what produces `ConfigLayerStack`.
    The docstring has the new specification and describes the various layers
    that will be loaded and the precedence order.
    - It uses the information from `LoaderOverrides` "twice," both in the
    spirit of legacy support:
    - We use one instances to derive an instance of `ConfigRequirements`.
    Currently, the only field in `managed_config.toml` that contributes to
    `ConfigRequirements` is `approval_policy`. This PR introduces
    `Constrained::allow_only()` to support this.
    - We use a clone of `LoaderOverrides` to derive
    `ConfigLayerSource::LegacyManagedConfigTomlFromFile` and
    `ConfigLayerSource::LegacyManagedConfigTomlFromMdm` layers, as
    appropriate. As before, this ends up being a "best effort" at enterprise
    controls, but is enforcement is not guaranteed like it is for
    `ConfigRequirements`.
    - Now we only create a "user" layer if `$CODEX_HOME/config.toml` exists.
    (Previously, a user layer was always created for `ConfigLayerStack`.)
    - Similarly, we only add a "session flags" layer if there are CLI
    overrides.
    - `config_loader/state.rs` contains the updated implementation for
    `ConfigLayerStack`. Note the public API is largely the same as before,
    but the implementation is quite different. We leverage the fact that
    `ConfigLayerSource` is now `PartialOrd` to ensure layers are in the
    correct order.
    - A `Config` constructed via `ConfigBuilder.build()` will use
    `load_config_layers_state()` to create the `ConfigLayerStack` and use
    the associated `ConfigRequirements` when constructing the `Config`
    object.
    - That said, a `Config` constructed via
    `Config::load_from_base_config_with_overrides()` does _not_ yet use
    `ConfigBuilder`, so it creates a `ConfigRequirements::default()` instead
    of loading a proper `ConfigRequirements`. I will fix this in a
    subsequent PR.
    
    Then the following files are mostly test changes:
    
    ```
    codex-rs/app-server/tests/suite/v2/config_rpc.rs
    codex-rs/core/src/config/service.rs
    codex-rs/core/src/config_loader/tests.rs
    ```
    
    Again, because we do not always include "user" and "session flags"
    layers when the contents are empty, `ConfigLayerStack` sometimes has
    fewer layers than before (and the precedence order changed slightly),
    which is the main reason integration tests changed.
  • feat: change ConfigLayerName into a disjoint union rather than a simple enum (#8095)
    This attempts to tighten up the types related to "config layers."
    Currently, `ConfigLayerEntry` is defined as follows:
    
    
    https://github.com/openai/codex/blob/bef36f4ae765f471d7cd69372fcf1b92c8f0367a/codex-rs/core/src/config_loader/state.rs#L19-L25
    
    but the `source` field is a bit of a lie, as:
    
    - for `ConfigLayerName::Mdm`, it is
    `"com.openai.codex/config_toml_base64"`
    - for `ConfigLayerName::SessionFlags`, it is `"--config"`
    - for `ConfigLayerName::User`, it is `"config.toml"` (just the file
    name, not the path to the `config.toml` on disk that was read)
    - for `ConfigLayerName::System`, it seems like it is usually
    `/etc/codex/managed_config.toml` in practice, though on Windows, it is
    `%CODEX_HOME%/managed_config.toml`:
    
    
    https://github.com/openai/codex/blob/bef36f4ae765f471d7cd69372fcf1b92c8f0367a/codex-rs/core/src/config_loader/layer_io.rs#L84-L101
    
    All that is to say, in three out of the four `ConfigLayerName`, `source`
    is a `PathBuf` that is not an absolute path (or even a true path).
    
    This PR tries to uplevel things by eliminating `source` from
    `ConfigLayerEntry` and turning `ConfigLayerName` into a disjoint union
    named `ConfigLayerSource` that has the appropriate metadata for each
    variant, favoring the use of `AbsolutePathBuf` where appropriate:
    
    ```rust
    pub enum ConfigLayerSource {
        /// Managed preferences layer delivered by MDM (macOS only).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        Mdm { domain: String, key: String },
        /// Managed config layer from a file (usually `managed_config.toml`).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        System { file: AbsolutePathBuf },
        /// Session-layer overrides supplied via `-c`/`--config`.
        SessionFlags,
        /// User config layer from a file (usually `config.toml`).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        User { file: AbsolutePathBuf },
    }
    ```
  • fix: introduce AbsolutePathBuf as part of sandbox config (#7856)
    Changes the `writable_roots` field of the `WorkspaceWrite` variant of
    the `SandboxPolicy` enum from `Vec<PathBuf>` to `Vec<AbsolutePathBuf>`.
    This is helpful because now callers can be sure the value is an absolute
    path rather than a relative one. (Though when using an absolute path in
    a Seatbelt config policy, we still have to _canonicalize_ it first.)
    
    Because `writable_roots` can be read from a config file, it is important
    that we are able to resolve relative paths properly using the parent
    folder of the config file as the base path.
  • [app-server-protocol] Add types for config (#7658)
    Currently the config returned by `config/read` in untyped. Add types so
    it's easier for client to parse the config. Since currently configs are
    all defined in snake case we'll keep that instead of using camel case
    like the rest of V2.
    
    Sample output by testing using the app server test client:
    ```
    {
    <   "id": "f28449f4-b015-459b-b07b-eef06980165d",
    <   "result": {
    <     "config": {
    <       "approvalPolicy": null,
    <       "compactPrompt": null,
    <       "developerInstructions": null,
    <       "features": {
    <         "experimental_use_rmcp_client": true
    <       },
    <       "forcedChatgptWorkspaceId": null,
    <       "forcedLoginMethod": null,
    <       "instructions": null,
    <       "model": "gpt-5.1-codex-max",
    <       "modelAutoCompactTokenLimit": null,
    <       "modelContextWindow": null,
    <       "modelProvider": null,
    <       "modelReasoningEffort": null,
    <       "modelReasoningSummary": null,
    <       "modelVerbosity": null,
    <       "model_providers": {
    <         "local": {
    <           "base_url": "http://localhost:8061/api/codex",
    <           "env_http_headers": {
    <             "ChatGPT-Account-ID": "OPENAI_ACCOUNT_ID"
    <           },
    <           "env_key": "CHATGPT_TOKEN_STAGING",
    <           "name": "local",
    <           "wire_api": "responses"
    <         }
    <       },
    <       "model_reasoning_effort": "medium",
    <       "notice": {
    <         "hide_gpt-5.1-codex-max_migration_prompt": true,
    <         "hide_gpt5_1_migration_prompt": true
    <       },
    <       "profile": null,
    <       "profiles": {},
    <       "projects": {
    <         "/Users/celia/code": {
    <           "trust_level": "trusted"
    <         },
    <         "/Users/celia/code/codex": {
    <           "trust_level": "trusted"
    <         },
    <         "/Users/celia/code/openai": {
    <           "trust_level": "trusted"
    <         }
    <       },
    <       "reviewModel": null,
    <       "sandboxMode": null,
    <       "sandboxWorkspaceWrite": null,
    <       "tools": {
    <         "viewImage": null,
    <         "webSearch": null
    <       }
    <     },
    <     "origins": {
    <       "features.experimental_use_rmcp_client": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.base_url": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.env_http_headers.ChatGPT-Account-ID": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.env_key": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.name": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.wire_api": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_reasoning_effort": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "notice.hide_gpt-5.1-codex-max_migration_prompt": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "notice.hide_gpt5_1_migration_prompt": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "projects./Users/celia/code.trust_level": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "projects./Users/celia/code/codex.trust_level": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "projects./Users/celia/code/openai.trust_level": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "tools.web_search": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       }
    <     }
    <   }
    < }
    ```
  • [app-server] make file_path for config optional (#7560)
    When we are writing to config using `config/value/write` or
    `config/batchWrite`, it always require a `config/read` before it right
    now in order to get the correct file path to write to. make this
    optional so we read from the default user config file if this is not
    passed in.