Commit Graph

6179 Commits

  • feat: add skills extension scaffold (#25953)
    ## Disclaimer
    This is only here for iteration purpose! Do not make any code rely on
    this
    
    ## Why
    
    Skills still live behind `codex-core` discovery and injection paths, but
    the extension system needs an authority-aware home before that logic can
    move. This adds that boundary without changing current skills behavior,
    and keeps host, executor, and remote skills distinct so future
    list/read/search flows do not collapse back to ambient local paths.
    
    ## What changed
    
    - Add the `codex-skills-extension` workspace/Bazel crate under
    `ext/skills`.
    - Define the initial catalog, authority, provider, and turn-state types
    for authority-bound skill packages and resources.
    - Register placeholder thread/config/prompt/turn lifecycle contributors
    plus host, executor, and remote provider aggregation points.
    - Capture the remaining extraction work as TODOs, including the missing
    extension API hooks needed for per-turn catalog construction and typed
    skill injection.
    - Keep plugins outside the runtime skills model: plugin-installed skills
    are treated as materialized host-owned skill sources once available.
    
    ## Verification
    
    - Not run locally.
  • Expose standalone image generation in code mode (#25923)
    ## Why
    
    Standalone image generation remained top-level-only in code-mode
    sessions.
    
    ## What changed
    
    - Change imagegen exposure from `DirectModelOnly` to `Direct`.
    - Keep direct-mode access while enabling nested code-mode access.
    - Add a focused regression test for the exposure contract.
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
  • config: remove dead profile sandbox fallback (#25943)
    ## Why
    
    `profile_sandbox_mode` was left over from the old selected legacy
    profile path. Production now always derives permissions without that
    value, and legacy profile contents are ignored, so keeping a parameter
    that is always `None` makes `derive_permission_profile` look like it
    still supports a fallback that no longer exists.
    
    ## What Changed
    
    - Removed the `profile_sandbox_mode` argument from
    `ConfigToml::derive_permission_profile`.
    - Updated the production caller and legacy sandbox-policy test helper to
    match.
    - Dropped the stale unselected legacy-profile sandbox test that only
    protected the removed fallback shape.
    
    ## Verification
    
    - `just test -p codex-config`
    - `just test -p codex-core 'config::'`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/25943).
    * #25926
    * __->__ #25943
  • Add remote request permissions integration coverage (#25867)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. This PR (#25867) - Add remote request permissions integration
    coverage: verifies the selected remote environment across request,
    approval, grant reuse, and exec.
    
    This PR is stacked on #25862 and should be reviewed after #25850,
    #25858, and #25862.
    
    ## Why
    
    The environment-scoped permission stack needs one end-to-end check that
    exercises the CCA-shaped path, not only unit-level parsing. This
    verifies that a model-sent `environmentId` on `request_permissions`
    reaches the approval event, stores the grant under the selected
    environment, and is reused by a later tool call in that same
    environment.
    
    ## What Changed
    
    - Adds a remote executor integration test for `request_permissions` with
    `environmentId: remote` and a relative write root.
    - Asserts the permission event reports the remote environment and cwd,
    and that the normalized grant resolves under the remote cwd.
    - Approves the grant, then runs a remote `exec_command` without explicit
    per-call permissions and verifies it completes without another exec
    approval and writes only in the remote filesystem.
    
    ## Verification
    
    - Not run locally per instruction.
    - `git diff --check`
  • [codex] Keep hosted tools visible in code-only mode (#25890)
    ## Why
    
    `code_mode_only` moved ordinary runtime tools behind `exec`, but it also
    hid hosted Responses tools. Hosted `web_search` and `image_generation`
    do not have a nested `exec` runtime path, so code-only sessions lost
    those capabilities entirely even when their existing provider, auth,
    model, and configuration gates passed.
    
    ## What changed
    
    - Keep hosted Responses tools top-level in `code_mode_only` sessions
    after their existing gates pass.
    - Preserve the existing nested-tool behavior for ordinary runtimes and
    the direct-only behavior for multi-agent v2 tools.
    - Add planner coverage for `code_mode_only` with default multi-agent v2
    settings, hosted live web search, and hosted image generation.
    
    ## Verification
    
    - Added focused regression coverage in
    `codex-rs/core/src/tools/spec_plan_tests.rs`.
    - Left execution to CI per repository workflow.
  • Split cloud config bundle service modules (#25668)
    ## Summary
    
    - Splits the monolithic `codex-cloud-config` implementation into focused
    modules.
    - Keeps behavior unchanged from the preceding config bundle runtime
    switch.
    
    ## Details
    
    This is the reviewability follow-up after the lineage-preserving
    migration PRs. The split separates backend transport, loader
    construction, cache handling, metrics, validation, service
    orchestration, and focused tests into named files.
    
    Verification: `just fmt`; `just test -p codex-cloud-config`.
  • core: stop passing legacy SandboxPolicy to guardian reviews (#25911)
    ## Why
    
    Guardian review turns already submit a read-only `PermissionProfile`,
    which is the permissions model the runtime should honor. Passing the
    equivalent legacy `SandboxPolicy` through `ThreadSettingsOverrides`
    keeps two representations of the same read-only constraint alive on this
    path and makes the guardian flow depend on compatibility plumbing that
    is being phased out.
    
    ## What Changed
    
    - Set `sandbox_policy` to `None` when the guardian review session
    submits its child `Op::UserInput`.
    - Keep `permission_profile: Some(PermissionProfile::read_only())` and
    `approval_policy: Some(AskForApproval::Never)`, so the guardian review
    remains read-only and cannot request approvals.
    - Remove the now-unused `SandboxPolicy` import and redundant comment
    from `codex-rs/core/src/guardian/review_session.rs`.
    
    ## Verification
    
    Not run locally; this is a narrow cleanup of redundant thread-settings
    override state.
  • fix: update image generation test helper rename (#25938)
    ## Summary
    - update the app-server image generation integration test to use
    `TestAppServer`
    - completes the test helper rename from #25701 for this newer test file
    
    ## Validation
    - `cargo fmt -- --config imports_granularity=Item`
    - `cargo check -p codex-app-server --test all`
    
    Note: `just fmt` ran Rust formatting but failed on Python/SDK formatting
    because the sandbox could not access the local `uv` cache.
  • Switch runtime to cloud config bundle (#24622)
    ## Summary
    
    - Adapts the moved `codex-cloud-config` crate from the legacy cloud
    requirements endpoint to the new config bundle endpoint.
    - Switches runtime consumers from `CloudRequirementsLoader` to
    `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
    config and requirements.
    - Removes the legacy cloud requirements domain loader path.
    
    ## Details
    
    This intentionally keeps `codex-cloud-config` monolithic for review
    lineage: the previous PR establishes the crate move, and this PR shows
    the behavior change against that moved implementation. A follow-up PR
    splits the module back into focused files.
    
    The new bundle path preserves the important cloud requirements loader
    semantics where intended: account-scoped signed cache, 30 minute TTL, 5
    minute refresh cadence, retry/backoff, auth recovery, and fail-closed
    startup loading. The cached payload changes from a single requirements
    TOML string to the backend-delivered bundle, and validation rejects
    malformed config or requirements fragments before cache write/use.
  • Populate workspace kind on Codex turn events (#25135)
    ## Summary
    - carry `workspace_kind` from Responses API client metadata into the
    turn resolved analytics fact
    - serialize the optional value on `codex_turn_event`
    - cover both the turn metadata source and turn event serialization
    
    The `workspace_kind` tells us whether a thread had a project attached vs
    projectless. this is an indicator for who is adopting Codex for
    knowledge work outside of coding
    
    ## Testing
    - `env UV_CACHE_DIR=/private/tmp/uv-cache
    /private/tmp/cargo-tools/bin/just fmt`
    - `env PATH=/private/tmp/cargo-tools/bin:$PATH
    CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
    /private/tmp/cargo-tools/bin/just test -p codex-analytics`
    - `env PATH=/private/tmp/cargo-tools/bin:$PATH
    CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
    /private/tmp/cargo-tools/bin/just test -p codex-core turn_metadata`
    
    Paired with openai/openai#970661, which keeps forwarding the same
    metadata key through Responses API headers.
  • Fix Windows running thread resume path normalization (#25509)
    ## Why
    
    Fixes #24944.
    
    On Windows, app-server resume could reject an active running thread when
    the requested session path used normal `C:\...` form and the
    already-running path used verbatim `\\?\C:\...` form. The paths point at
    the same JSONL file, but the resume stale-path guard compared raw
    `PathBuf`s, so desktop resume and heartbeat flows could fail with a
    mismatched-path error.
    
    ## What Changed
    
    - Compare requested and active rollout paths with
    `path_utils::paths_match_after_normalization`.
    - Extend the existing running-thread mismatched-path test with a
    Windows-only same-file resume case before the stale-path rejection.
    
    ## Verification
    
    - `just test -p codex-app-server
    thread_resume_rejects_mismatched_path_for_running_thread_id`
  • Propagate permission approval environment id (#25862)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. This PR (#25862) - Propagate permission approval environment id:
    carries the selected environment id through approval events, app-server
    requests, TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    This PR is stacked on #25858, and #25867 is stacked on this PR.
    
    ## Why
    
    PR2 lets the model bind a `request_permissions` call to a selected
    environment, but the approval event and client-facing request still
    needed to carry that binding. For CCA, the user-facing prompt and
    delegated approval path should know which environment the grant applies
    to instead of relying on cwd alone.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsEvent`.
    - Emit the selected environment id from core permission approval events.
    - Preserve the environment id through delegate forwarding, including
    cwd-based delegated requests.
    - Added `environmentId` to app-server permission approval params,
    generated schema/TypeScript artifacts, and README examples.
    - Preserve and display the environment id in TUI permission approval
    prompts.
    - Updated focused core, app-server protocol, and TUI conversion
    coverage.
    
    ## Testing
    
    Not run locally per instruction. Performed read-only `git diff --check`.
  • Route standalone image generation through host finalization md (#25176)
    ## Why
    
    Standalone image-generation extensions emitted turn items through the
    low-level event path, bypassing host-owned finalization such as image
    persistence and contributor processing. At the same time, the
    generated-image save-path hint must remain visible to the model through
    the extension tool's `FunctionCallOutput`, rather than the legacy
    built-in developer-message path.
    
    ## What changed
    
    - Extended `ExtensionTurnItem` to support image-generation items while
    keeping the extension-facing emitter API limited to `emit_started` and
    `emit_completed`.
    - Routed extension completion through core `finalize_turn_item`, so
    standalone image-generation items receive host-owned processing and
    persisted `saved_path` values before publication.
    - Kept legacy built-in image generation on its existing
    developer-message hint path, while standalone image generation returns
    its deterministic saved-path hint in `FunctionCallOutput`.
    - Shared the image artifact path and output-hint formatting used by core
    and the image-generation extension.
    - Passed thread identity through extension tool calls so standalone
    image generation can construct the same intended artifact path as core.
    - Added an app-server integration test covering real standalone image
    generation, saved artifact publication, model-visible output hint
    wiring, and absence of the legacy developer-message hint.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-image-generation-extension`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-goal-extension`
    - `just test -p codex-memories-extension`
    - Targeted `codex-core` tests for image save history, extension
    completion finalization, and contributor execution
    - `just test -p codex-app-server
    standalone_image_generation_returns_saved_path_hint_to_model`
    - `just fix -p codex-core`
    - `just fix -p codex-image-generation-extension`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
  • Add environmentId to request_permissions (#25858)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. This PR (#25858) - Add `environmentId` to `request_permissions`: lets
    the model target a selected environment and resolves relative permission
    paths against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    This PR is stacked on #25850; #25862 and #25867 are stacked on this PR.
    
    ## Why
    
    PR1 made request-permission grants internally environment-keyed, but the
    model-facing `request_permissions` tool could still only target the
    primary environment. For CCA and multi-environment turns, the tool needs
    an explicit way to bind a permission request to a selected attached
    environment before resolving relative paths.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsArgs`, with
    `environment_id` accepted as an alias.
    - Exposed `environmentId` in the `request_permissions` tool schema and
    description.
    - Resolve the selected environment before parsing filesystem permission
    paths, so relative paths bind to the selected environment cwd.
    - Route validated tool calls through
    `request_permissions_for_environment` directly instead of duplicating
    environment lookup in `Session::request_permissions`.
    - Reject unknown environment ids with a model-facing error.
    - Updated focused request-permissions and Guardian call sites for the
    new optional field.
    
    ## Testing
    
    Not run locally per instruction.
  • [codex-analytics] Track CodexErr details in turn analytics (#25707)
    ## Summary
    - add analytics-only `CodexErr` telemetry to `codex_turn_event` while
    leaving existing `turn_error` unchanged
    - record terminal `CodexErr` facts from core immediately before the
    existing turn error event is sent
    - emit source-truth `codex_error_*` fields for downstream analytics,
    including the raw `CodexErr::InvalidRequest(String)` message as
    `codex_error_subreason`
    
    ## Validation
    - `just test -p codex-analytics`
    - attempted `just test -p codex-core`, but the local run timed out
    across unrelated integration suites in this environment and is not being
    used as validation
  • Key request-permission grants by environment (#25850)
    ## Stack
    
    1. This PR (#25850) - Key request-permission grants by environment:
    stores and applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    #25858, #25862, and #25867 are stacked on this PR and should be reviewed
    after it.
    
    ## Why
    
    Multi-environment CCA turns can attach both local and remote executors,
    but request-permission grants were still effectively cwd-only. Pending
    permission requests tracked a cwd, while stored turn/session grants had
    no environment identity, so sticky grants could be reused through the
    wrong executor context.
    
    This makes the first permission-grant step environment-aware without
    changing the external `request_permissions` payload shape: omitted
    environment targeting remains bound to the primary turn environment.
    
    ## What Changed
    
    - Store turn- and session-scoped request-permission grants by
    `environment_id`.
    - Keep the selected `TurnEnvironmentSelection` with pending
    `request_permissions` calls so approval responses normalize and record
    grants against the same environment.
    - Resolve relative `request_permissions` file paths against the primary
    turn environment cwd instead of deprecated `turn.cwd`.
    - Apply sticky grants in `shell`, `exec_command`, and `apply_patch` by
    selected environment id while still using the actual tool cwd for
    cwd-relative permission materialization.
    - Update Guardian and request-permissions coverage for the
    environment-keyed grant behavior.
    
    ## Testing
    
    Not run locally. Added or updated focused coverage for:
    
    - `request_permission_grants_are_environment_keyed`
    -
    `request_permissions_tool_resolves_relative_paths_against_primary_environment`
    - related Guardian/request-permissions sticky grant tests
  • [codex] Revert shared BuildBuddy Bazel wrapper (#25909)
    ## Why
    
    PR #25905 intentionally adds a failing `codex-core` unit test, but its
    [Bazel test on Windows
    check](https://github.com/openai/codex/actions/runs/26837526950/job/79135369259)
    passed. That shows the Bazel configuration introduced by #25156 is not
    behaving as expected, so revert it while the configuration can be
    investigated separately.
    
    ## What changed
    
    Revert #25156 in full, restoring the previous Bazel remote
    configuration, CI scripts, workflows, `rusty_v8` handling, and
    documentation. This removes the shared BuildBuddy wrapper and its tests.
    
    ## Validation
    
    Not run locally; this exact revert was prioritized for a fast rollback.
  • core: derive built-in permission profiles from raw policies (#25739)
    ## Why
    
    Permission profiles that extend a built-in profile should behave like
    other TOML inheritance: parent entries provide defaults, and child keys
    override matching fields before the profile is compiled.
    
    That was not true for `:workspace`. Previously, a profile with `extends
    = ":workspace"` seeded the compiled runtime
    `PermissionProfile::workspace_write()` policy and then appended child
    filesystem entries. A child override such as `":tmpdir" = "read"`
    therefore left the inherited `":tmpdir" = "write"` entry in the final
    policy. Since same-target `write` wins over `read` during runtime
    resolution, the child override was ineffective.
    
    This also needs a clear source of truth for the built-in profiles. The
    protocol-level sandbox policy constructors now define the raw built-in
    filesystem entries, and both `PermissionProfile` presets and
    config-profile inheritance derive from those same values.
    
    ## What Changed
    
    - Add a canonical `FileSystemSandboxPolicy::read_only()` constructor
    while keeping the read-only and workspace-write raw filesystem entries
    explicit and independent.
    - Derive `PermissionProfile::read_only()` from
    `FileSystemSandboxPolicy::read_only()`;
    `PermissionProfile::workspace_write()` continues to derive from
    `FileSystemSandboxPolicy::workspace_write()`.
    - Build extensible `:read-only` and `:workspace` parent profiles by
    projecting those canonical sandbox policies into
    `PermissionProfileToml`, then merge user overrides at the TOML layer
    before compilation.
    - Add config parsing support for `:slash_tmp` so the built-in
    `:workspace` parent can be expressed in the same TOML-shaped filesystem
    table as user profiles.
    - Document that `PermissionsToml::resolve_profile()` returns an
    already-merged `PermissionProfileToml`, and return that profile directly
    after removing the resolved-profile wrapper.
    - Extend the config test for `extends = ":workspace"` to assert that
    inherited `":slash_tmp" = "write"` is preserved and that a child
    `":tmpdir" = "read"` entry replaces the inherited `write` entry.
    
    ## Verification
    
    - `just test -p codex-config`
    - `just test -p codex-protocol`
    - `just test -p codex-core
    permissions_profiles_resolve_extends_parent_first_with_child_overrides`
    - `just test -p codex-core
    default_permissions_profile_can_extend_builtin_workspace`
    - `just test -p codex-core`
      - Result: 2596 passed, 4 failed, 1 timed out.
    - The failures were existing sandbox/environment-sensitive tests
    unrelated to this permissions change:
    
    `suite::user_shell_cmd::user_shell_command_does_not_set_network_sandbox_env_var`,
    
    `suite::user_shell_cmd::user_shell_command_history_is_persisted_and_shared_with_model`,
    
    `suite::abort_tasks::interrupt_persists_turn_aborted_marker_in_next_request`,
        `suite::abort_tasks::interrupt_tool_records_history_entries`, and
    
    `thread_manager::tests::start_thread_uses_all_default_environments_from_codex_home`.
  • Route Bazel CI through shared BuildBuddy remote config wrapper (#25156)
    ## Why
    
    Bazel remote configuration was selected in several CI scripts and
    workflow steps. That made the BuildBuddy tenant policy easy to duplicate
    and harder to audit, especially for fork pull requests that must not use
    the OpenAI tenant.
    
    This builds on
    [sluongng/buildbuddy-ci-host-routing](https://github.com/openai/codex/compare/main...sluongng:codex:sluongng/buildbuddy-ci-host-routing)
    and consolidates the policy in one place.
    
    ## What to do if this breaks you
    
    See `codex-rs/docs/bazel.md` for details. TLDR:
    
    1. make a BuildBuddy API key and put it in `~/.bazelrc`
    2. if you're an OpenAI employee, add `common
    --config=buildbuddy-openai-rbe` to `user.bazelrc` in the repo root
    
    Run `just bazel-test` to ensure it works.
    
    Note that `just bazel-remote-test` no longer exists, you need to select
    a remote configuration as documented to use RBE.
    
    ## What changed
    
    - Add `.github/scripts/run_bazel_with_buildbuddy.py` as the shared Bazel
    wrapper and Python library. It selects the OpenAI host only for trusted
    upstream GitHub Actions runs, routes keyed fork runs to the generic
    host, and falls back to local Bazel execution when no key is available.
    - Move endpoint selection into explicit `.bazelrc` configurations and
    update Bazel CI, query helpers, and `rusty_v8` staging to use the shared
    policy. Loading-phase target-discovery queries remain local.
    - Add wrapper and `rusty_v8` unit coverage, plus `just test-scripts` for
    the `.github/scripts` Python tests.
    - Document local Bazel usage, `user.bazelrc` setup, BuildBuddy
    configurations, and CI behavior in `codex-rs/docs/bazel.md`.
    
    ## Validation
    
    - `just test-scripts`
    - `bash -n .github/scripts/run-bazel-ci.sh
    .github/scripts/run-bazel-query-ci.sh
    .github/scripts/run-argument-comment-lint-bazel.sh
    scripts/list-bazel-clippy-targets.sh`
    - `python3 -m py_compile .github/scripts/run_bazel_with_buildbuddy.py
    .github/scripts/test_run_bazel_with_buildbuddy.py
    .github/scripts/test_rusty_v8_bazel.py
    .github/scripts/rusty_v8_bazel.py`
    - `ruff check .github/scripts/run_bazel_with_buildbuddy.py
    .github/scripts/test_run_bazel_with_buildbuddy.py
    .github/scripts/test_rusty_v8_bazel.py
    .github/scripts/rusty_v8_bazel.py`
  • Skip startup prewarm when websockets are disabled (#25868)
    ## Summary
    - skip startup websocket prewarm setup when the model client has
    Responses-over-WebSocket disabled
    - avoid making HTTP-only sessions build prewarm prompt/tool state that
    cannot produce a reusable websocket session
    
    ## Why
    Recent macOS timing flakes were timing out while waiting for first-turn
    events in HTTP-only core tests. Startup prewarm is only useful for
    websocket-capable providers, but it was scheduled for every session. For
    HTTP-only test providers this added unnecessary async startup work
    before the regular turn could reach the mocked response flow.
    
    ## Testing
    - bazel test //codex-rs/core:core-all-test
    --test_filter=suite::auto_review::remote_model_override_uses_catalog_model_for_strict_auto_review
    --test_output=errors
    - bazel test //codex-rs/core:core-all-test
    --test_filter=suite::request_permissions_tool::approved_folder_write_request_permissions_unblocks_later_apply_patch
    --test_output=errors
  • [app-server][core] Add connector-level Guardian reviewer overrides (#25167)
    Context: https://openai.slack.com/archives/C0B4JAF0Q2C/p1779912328647229
    
    ```
    approvals_reviewer = "auto_review"
    
    [apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
    enabled = true
    approvals_reviewer = "user"
    default_tools_approval_mode = "prompt"
    ```
    
    <img width="230" height="84" alt="Screenshot 2026-05-31 at 11 56 34 AM"
    src="https://github.com/user-attachments/assets/e319f8f7-0983-42a7-98cd-3302732fa406"
    />
    
    <img width="841" height="233" alt="Screenshot 2026-05-31 at 11 52 42 AM"
    src="https://github.com/user-attachments/assets/7ac76645-4e90-4d00-8242-f031146a22a5"
    />
    
    -------
    
    ```
    approvals_reviewer = "user"
    
    [apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
    enabled = true
    approvals_reviewer = "auto_review"
    default_tools_approval_mode = "prompt"
    ```
    <img width="195" height="83" alt="Screenshot 2026-05-31 at 12 02 27 PM"
    src="https://github.com/user-attachments/assets/3d374dc8-8aa2-466f-a13f-e4ed8567aa2e"
    />
    <img width="771" height="207" alt="Screenshot 2026-05-31 at 12 05 42 PM"
    src="https://github.com/user-attachments/assets/105c2575-68d6-4ca6-8e69-dc8c82da36a2"
    />
    
    
    
    ## Summary
    - add `apps.<connector_id>.approvals_reviewer` to override Guardian or
    user review routing per connected app
    - apply overrides across direct app MCP calls, delegated MCP prompts,
    and app-server MCP elicitation review while preserving global behavior
    for non-app MCP servers
    - expose and document the config through app-server v2 and generated
    schemas, while honoring global managed reviewer requirements
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Run Codex async main on a sized stack (#25847)
    ## Why
    
    `Runtime::block_on` executes the top-level future on the caller's OS
    thread, not on one of Tokio's worker threads. That matters for the
    interactive CLI because the Tokio runtime already configures larger
    worker stacks, while the process main thread can still have a smaller
    platform default stack.
    
    This showed up as a `/clear` crash on macOS: starting a fresh TUI thread
    reloads config, and the stack-heavy TOML deserialization path can
    overflow before the new session is actually started.
    
    ## What Changed
    
    - Run the regular `arg0_dispatch_or_else` async entrypoint on a named
    `codex-main` thread.
    - Give that thread the same `TOKIO_WORKER_STACK_SIZE_BYTES` stack budget
    already used for Tokio worker threads.
    - Keep `Arg0DispatchPaths` and the arg0 alias guard lifetime behavior
    the same.
    - Resume panics from the spawned main thread so panic behavior is
    preserved.
    
    ## Verification
    
    - `cargo check -p codex-cli` currently fails because the top-level
    CLI/TUI future is not `Send` under the new thread boundary.
  • flake: Keep plugin test homes alive (#25857)
    ## Summary
    
    Keep the full `TestCodex` harness alive in plugin integration tests
    instead of returning only the `CodexThread`.
    
    ## Why
    
    The helper was moving a temporary `codex_home` into `TestCodex`, then
    immediately dropping the harness and returning only the thread. For
    plugin MCP tests, the MCP server cwd is inside that temporary home. If
    the temp directory is removed while MCP startup is still racing, the
    server launch can fail with `No such file or directory`.
    
    Keeping the harness in scope keeps the temp home alive for the test
    duration and removes the lifetime race behind the recent
    `explicit_plugin_mentions_inject_plugin_guidance` flake.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core
    explicit_plugin_mentions_inject_plugin_guidance`
  • Reduce stack pressure in session startup and config rebuilds (#25844)
    ## Why
    
    `/clear` starts a fresh thread with `InitialHistory::Cleared`, which
    re-enters the thread/session startup path. That path now builds large
    async futures through `ThreadManagerState::spawn_thread_with_source`,
    `Codex::spawn`, and `Session::new`. Separately, TUI config rebuilds for
    cwd and permission-profile changes build a similarly heavy
    `ConfigBuilder::build()` future inside the app task. In debug and Bazel
    runs, those call chains can put enough state on the caller stack to
    abort before startup or config refresh completes.
    
    This change keeps the behavior the same while moving the heaviest future
    frames off the caller stack.
    
    ## What changed
    
    - Box `Codex::spawn(...)` in `codex-rs/core/src/thread_manager.rs`
    before awaiting it from `spawn_thread_with_source`.
    - Box `Session::new(...)` in `codex-rs/core/src/session/mod.rs` before
    awaiting it from `Codex::spawn_internal`.
    - Route `ConfigBuilder::build()` through a small `tokio::spawn` helper
    in `codex-rs/tui/src/app/config_persistence.rs` so cwd and
    permission-profile config rebuilds run on a runtime worker stack while
    preserving error context.
    
    ## Verification
    
    CI is running on the PR.
    
    No new targeted tests were added. This is a mechanical stack-pressure
    reduction that keeps the existing behavior and error propagation intact.
  • Test runtime selector before first turn (#25724)
    Stack split from #25708. Original PR intentionally left open. This fifth
    PR adds coverage that a remotely selected multi-agent runtime is applied
    when the model is selected before the first turn.
  • Test remote multi-agent runtime selector override (#25723)
    Stack split from #25708. Original PR intentionally left open. This
    fourth PR adds coverage that remote model multi-agent runtime selectors
    override local feature flag defaults.
  • fix: main oops (#25840)
    Fix main, comment is self-explainatory
  • session: keep startup prewarm aligned with resolved multi-agent runtime (#25841)
    ## Why
    
    Follow-up to #25722. Startup prewarm builds a preview `TurnContext`
    before the first real turn so it can precompute the initial prompt and
    tool surface. After the per-thread runtime work landed, that preview
    path still recomputed multi-agent mode from `model_info` and feature
    defaults instead of reusing the runtime the session had already resolved
    from persisted metadata or inheritance.
    
    That could leave the prewarmed session primed for a different
    multi-agent mode than the first real turn, which is especially risky
    because collaboration tool exposure depends on
    `turn_context.multi_agent_version`.
    
    ## What changed
    
    - In the `TurnMultiAgentRuntime::Preview` path, prefer
    `Session::multi_agent_version()` when it is already known.
    - Only fall back to `model_info.multi_agent_version` and feature
    defaults when the session has not resolved a runtime yet.
    - Keep preview mode read-only: this still avoids storing a runtime
    during startup prewarm.
    
    ## Testing
    
    - Not run (small runtime-selection follow-up)
  • Resolve per-thread multi-agent runtime (#25722)
    Stack split from #25708. Original PR intentionally left open. This third
    PR resolves the effective per-thread multi-agent runtime from persisted
    metadata, inherited runtime, and current model selection.
  • Persist multi-agent runtime metadata (#25721)
    Stack split from #25708. Original PR intentionally left open. This
    second PR persists multi-agent runtime metadata through thread creation,
    rollout recording, and thread storage.
  • Add multi-agent runtime metadata types (#25720)
    Stack split from #25708. Original PR intentionally left open. This first
    PR adds the multi-agent runtime metadata types and catalog plumbing used
    by the rest of the stack.
  • feat: reuse compressed rollout search snippets (#25814)
    ## Summary
    - teach rollout search to return precomputed snippets for compressed
    rollouts
    - reuse those snippets in local thread search instead of reopening
    matching compressed files
    - keep the no-`rg` fallback single-pass and add regression coverage for
    the compressed path
    
    ## Why
    `thread/search` currently decodes matching compressed rollouts twice:
    once to discover the matching path and again to extract the snippet
    shown in results. That defeats a meaningful part of the compressed-read
    optimization work.
    
    ## Impact
    Compressed rollout hits now pay one decode pass on the search path while
    plain `.jsonl` hits keep the existing ripgrep-driven flow.
    
    ## Validation
    - `just test -p codex-rollout`
    - `just test -p codex-thread-store`
    - `just fix -p codex-rollout`
    - `just fix -p codex-thread-store`
    - `just fmt`
  • [codex] Validate plugin skill base names (#25782)
    ## Summary
    
    - Validate skill base name length before plugin namespacing.
    - Bound the composed `plugin:skill` qualified name to 128 characters.
    - Keep plugin skill runtime names in the existing `plugin:skill` form.
    - Add regression tests for the max qualified-name boundary and rejection
    path.
    
    ## Root Cause
    
    Plugin skills are represented as `plugin_name:skill_name`, but the
    loader previously applied the 64-character skill name limit after adding
    the plugin namespace. Moving that check to the base name fixes valid
    plugin skills with longer namespaces, and the separate 128-character
    qualified-name limit keeps model-visible skill names bounded.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core-skills plugin_skill_name_length_limit`
    - `git diff --check`
  • [codex] Move plugin discoverable logic into core-plugins (#25783)
    ## Summary
    - Move plugin discoverable recommendation filtering from `codex-core`
    into `codex-core-plugins` behind `ToolSuggestPluginDiscoveryInput`.
    - Keep `codex-core` as a thin adapter from `Config` to the core-plugins
    API and back to `DiscoverablePluginInfo`.
    - Keep the existing discoverable allowlist private to the core-plugins
    implementation.
    
    ## Validation
    - `just fmt`
    - `just test -p codex-core list_tool_suggest_discoverable_plugins`
    - `git diff --check`
    - Read-only subagent review: no findings
  • [codex] Cache remote plugin catalog for suggestions (#25457)
    ## Summary
    - cache the global remote plugin catalog when remote plugin listing runs
    and warm it during startup
    - use the cached remote catalog in plugin install recommendations with
    canonical `plugin@openai-curated-remote` ids
    - reuse the session `PluginsManager` for plugin recommendations so
    remote cache state is visible on the recommend path
    - skip core installed-state verification for remote plugin install
    suggestions while leaving local plugin and connector verification
    unchanged
    
    ## Testing
    - `just fmt`
    - `git diff --check`
    - `cargo test -p codex-core
    list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins`
    - `cargo test -p codex-core
    remote_plugin_install_suggestions_skip_core_installed_verification`
    - `cargo test -p codex-app-server
    plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
    
    Earlier focused checks during the same branch: codex-tools TUI filter
    test, request_plugin_install tests, and codex-app-server build.
  • [codex] Add plugin list JSON output (#25330)
    ## Summary
    - add `--json` output to `codex plugin list` with `installed` and
    `available` arrays
    - add `--available` for JSON output only; using it without `--json` is
    rejected
    - keep the existing non-JSON table output unchanged
    - add CLI coverage for JSON installed/available output and the
    `--available`/`--json` requirement
    
    ## Validation
    - `just test -p codex-cli plugin_list`
    - `just fix -p codex-cli`
    - `git diff --check`
    
    Note: `just fmt` ran Rust formatting first, then failed in the Python
    ruff step because `openai-codex-cli-bin==0.132.0` has no wheel for this
    Linux platform.
  • feat: show enterprise monthly credit limits in status (#24812)
    ## Summary
    
    Enterprise users can have an effective monthly credit limit, but Codex
    `/status` currently drops that metadata from the account-usage response.
    
    This change adds the optional `spend_control.individual_limit`
    projection to the existing rate-limit snapshot flow. The backend client
    reads the monthly limit, app-server exposes it as `individualLimit`, and
    the TUI renders a `Monthly credit limit` row through the existing
    progress-bar renderer.
    
    When the backend does not return an effective monthly limit, existing
    rate-limit behavior is unchanged.
    
    ## Existing backend state
    
    The account-usage backend already returns the effective monthly limit
    and current usage together:
    
    ```json
    {
      "spend_control": {
        "reached": false,
        "individual_limit": {
          "limit": "25000",
          "used": "8000",
          "remaining": "17000",
          "used_percent": 32,
          "remaining_percent": 68,
          "reset_after_seconds": 86400,
          "reset_at": 1778137680
        }
      }
    }
    ```
    
    Before this change, Codex projected rolling `primary` and `secondary`
    windows plus `credits`. It ignored `spend_control.individual_limit`, so
    app-server clients and `/status` could not render the monthly cap.
    
    The updated flow is:
    
    ```text
    account usage backend
      -> backend-client reads spend_control.individual_limit
      -> existing rate-limit snapshot carries optional individual_limit
      -> app-server exposes optional individualLimit
      -> TUI renders Monthly credit limit
    ```
    
    ## App-server contract
    
    `account/rateLimits/read` and sparse `account/rateLimits/updated`
    notifications now include an additive nullable
    `rateLimits.individualLimit` field:
    
    ```json
    {
      "individualLimit": {
        "limit": "25000",
        "used": "8000",
        "remainingPercent": 68,
        "resetsAt": 1778137680
      }
    }
    ```
    
    In an `account/rateLimits/read` response, `null` means no monthly limit
    is available. `account/rateLimits/updated` remains a sparse rolling
    notification: clients merge available values into their most recent
    `account/rateLimits/read` snapshot or refetch. Nullable account metadata
    in a rolling notification does not clear a previously observed value.
    
    ## Design decisions
    
    - Extend the existing rate-limit snapshot instead of introducing a
    separate request or wire-level update protocol.
    - Keep the Codex projection narrow: `/status` needs the effective limit,
    current usage, remaining percentage, and reset timestamp.
    - Render the monthly row through the existing progress-bar renderer,
    with one optional detail line for `8,000 of 25,000 credits used`.
    - Keep the backend response optional so existing accounts and older
    usage states preserve their current behavior.
    - Preserve cached monthly metadata when sparse rolling notifications
    omit it. Live account-usage reads remain authoritative and can clear a
    removed limit.
    
    ## Visual evidence
    
    ```text
     Monthly credit limit:   [██████████████░░░░░░] 68% left (resets 07:08 on 7 May)
                             8,000 of 25,000 credits used
    ```
    
    Snapshot:
    `codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_enterprise_monthly_credit_limit.snap`
    
    ## Testing
    
    Tests: generated app-server schema verification, protocol tests,
    backend-client tests, app-server integration coverage, TUI snapshot
    coverage, formatting, and workspace lint cleanup.
  • feat(remote-control): add pairing start (#25675)
    ## Why
    
    Remote control enrollment authorizes a desktop server, but app-server v2
    did not expose the follow-up pairing operation needed to mint a
    short-lived controller pairing artifact from that enrolled server.
    Clients need a narrow RPC that starts pairing without exposing the
    backend `serverId` or conflating pairing with websocket connection
    state.
    
    Issue: N/A; internal remote-control pairing API change.
    
    ## What Changed
    
    Added experimental app-server v2 `remoteControl/pairing/start` with
    `manualCode` input and `pairingCode`, nullable `manualPairingCode`,
    `environmentId`, and Unix-seconds `expiresAt` output. The method
    serializes under its own `global("remote-control-pairing")` scope and is
    documented in `app-server/README.md`.
    
    Extended the remote-control transport with private `/server/pair`
    request/response types and normalized `pair_url` handling. Pairing uses
    the current enrolled server bearer, refreshes that bearer when needed,
    keeps backend `server_id` private, validates returned `server_id` and
    `environment_id` against the current enrollment, and preserves backend
    status/header/body context for failures and malformed responses.
    
    Wired the request through `RemoteControlRequestProcessor` and
    `MessageProcessor`, mapping unavailable/disabled pairing to
    `invalid_request` and backend failures to internal errors.
    
    ## Verification
    
    - `just test -p codex-app-server-transport`
    - `just test -p codex-app-server
    remote_control_pairing_start_returns_pairing_artifacts`
  • Handle invalid plugin skills manifest field (#25717)
    ## Summary
    - Treat invalid `plugin.json` `skills` shapes as a field-level warning
    instead of rejecting the whole manifest
    - Keep valid string path behavior unchanged and continue falling back to
    the default `skills/` root
    - Add regression coverage for array-shaped `skills`
    
    ## Tests
    - `just fmt`
    - `cargo test -p codex-core-plugins`
  • Move cloud requirements crate to cloud config (#24621)
    ## Summary
    
    - Moves the existing `codex-cloud-requirements` crate to
    `codex-cloud-config`.
    - Updates workspace dependencies and imports to the new crate name.
    - Intentionally keeps runtime behavior unchanged: this still fetches the
    legacy cloud requirements endpoint.
    
    ## Details
    
    This PR exists to make the lineage obvious before the bundle migration.
    GitHub should show the old `codex-rs/cloud-requirements/src/lib.rs`
    implementation as moved to `codex-rs/cloud-config/src/lib.rs`, rather
    than as unrelated new code.
    
    The follow-up PR adapts this moved crate to the new config bundle API
    and switches runtime consumers over.
  • app-server: remove experimental persist_extended_history bool flag (#25712)
    ## Summary
    
    Remove the dead experimental `persistExtendedHistory` app-server flag
    and collapse rollout persistence to the single policy app-server already
    used.
    
    ## What Changed
    
    - Removed `persistExtendedHistory` from v2 thread start/resume/fork
    params and deleted its deprecation notice path.
    - Removed the persistence-mode enums and plumbing through core, rollout,
    and thread-store.
    - Made rollout filtering mode-free, keeping the existing limited
    persisted-history behavior.
    
    ## Test Plan
    
    - `just write-app-server-schema`
    - `cargo nextest run --no-fail-fast -p codex-app-server-protocol
    schema_fixtures`
    - `cargo nextest run --no-fail-fast -p codex-app-server
    thread_shell_command_history_responses_exclude_persisted_command_executions`
    - `cargo nextest run --no-fail-fast -p codex-rollout -p
    codex-thread-store`
    - final `rg` for removed flag/type names
  • Wire managed MITM CA trust into child env (#22668)
    ## Stack
    1. Parent PR: #18240 uses named MITM permissions config.
    2. This PR wires managed MITM CA trust into spawned child processes.
    
    ## Why
    When Codex terminates HTTPS for limited mode or MITM hooks, child HTTPS
    clients need to trust Codex's managed MITM CA. Exporting proxy URLs
    alone is not enough, but blindly replacing user CA settings would be
    wrong: it can break custom enterprise/test roots, leak unreadable CA
    files into generated bundles, or make the child env disagree with its
    sandbox policy.
    
    ## Summary
    1. Build immutable managed CA bundles under `$CODEX_HOME/proxy` that
    include native roots, the managed MITM CA, and only inherited or
    command-scoped CA bundles the child is allowed to read.
    2. Export curated CA env vars alongside managed proxy env vars while
    preserving user CA override semantics, including nested Codex
    `SSL_CERT_FILE` precedence.
    3. Thread generated CA bundle paths into child sandbox readable roots,
    including debug sandbox execution, so the exported env vars work inside
    sandboxed commands.
    4. Remove only Codex-generated MITM CA bundle env when a child
    intentionally drops managed proxying for escalation or no-proxy retry.
    5. Document the managed CA bundle behavior and cover env injection,
    per-child bundle generation, sandbox readable roots, and no-proxy
    cleanup in tests.
    
    ## Validation
    1. Ran `just test -p codex-network-proxy`.
    2. Ran `just test -p codex-protocol`.
    3. Ran `just fix -p codex-network-proxy -p codex-protocol`.
    4. Tried focused `codex-core` validation, but the crate currently fails
    to compile in `core/tests/suite/guardian_review.rs` because an existing
    `Op::UserInput` initializer is missing `additional_context`.
    
    ---------
    
    Co-authored-by: Eva Wong <evawong@openai.com>
  • Reject directory rollout paths for pathless side chats (#25661)
    ## Why
    
    Fixes openai/codex#20944.
    
    Desktop side chats are intentionally ephemeral and pathless. They can
    still accept live turns while loaded, but after a reload there is no
    persisted rollout to resume. In the reported failure mode, Desktop could
    send `$CODEX_HOME` as the resume/fork path for one of these pathless
    side chats.
    
    `thread/resume` and `thread/fork` prefer an explicit `path` over
    `threadId`, and rollout path lookup only checked that a candidate
    existed. That let `$CODEX_HOME` pass as a rollout path, so the later
    rollout reader tried to open a directory and surfaced the low-level `Is
    a directory` error.
    
    ## What Changed
    
    - Reject explicit rollout paths that resolve to a directory or other
    non-file before attempting to read rollout history.
    - Make `codex_rollout::existing_rollout_path` return only plain or
    compressed rollout candidates that are actual files.
    - Add an app-server regression test that creates an ephemeral fork, runs
    a turn while the side thread is loaded, simulates reload, then verifies
    both `thread/resume` and `thread/fork` reject `$CODEX_HOME` with `path
    is a directory` instead of the OS-level directory-read error.
    - Rebase over the `TestAppServer` rename and update the remaining stale
    test harness call sites to use `TestAppServer` with `app_server` local
    variables.
    
    Relevant code:
    
    - `thread-store/src/local/read_thread.rs` validates explicit rollout
    paths before rollout reading:
    https://github.com/openai/codex/blob/25b47c8f425d351aaba4baa955a8092064a1707b/codex-rs/thread-store/src/local/read_thread.rs#L146-L165
    - `rollout/src/compression.rs` now requires file metadata for plain and
    compressed rollout candidates:
    https://github.com/openai/codex/blob/25b47c8f425d351aaba4baa955a8092064a1707b/codex-rs/rollout/src/compression.rs#L940-L950
    - The repro test covers the pathless ephemeral side-chat reload case:
    https://github.com/openai/codex/blob/25b47c8f425d351aaba4baa955a8092064a1707b/codex-rs/app-server/tests/suite/v2/thread_fork.rs#L774-L886
    
    ## Verification
    
    - `just test -p codex-app-server
    pathless_ephemeral_thread_rejects_codex_home_path_after_reload`
  • fix(tui): clarify footer shortcut overlay hints (#25625)
    ## Why
    
    The TUI shortcut overlay used static labels for `Tab` and `Ctrl+C`, even
    though both keys change behavior while a task is running. That made the
    visible help misleading: idle `Tab` submits rather than queues, and
    active-turn `Ctrl+C` interrupts rather than exits.
    
    Closes #25531.
    Closes #25564.
    
    ## What Changed
    
    - Pass task-running state into the shortcut overlay renderer.
    - Render `Tab` as `submit message` while idle and `queue message` while
    work is running.
    - Render `Ctrl+C` as `exit` while idle and `interrupt` while work is
    running.
    - Add snapshot coverage for the active-work shortcut overlay and update
    idle overlay snapshots.
    
    ## How to Test
    
    1. Start Codex and open the shortcut overlay with `?` while no task is
    running.
    2. Confirm the overlay shows `tab to submit message` and `ctrl + c to
    exit`.
    3. Start a task, then open or keep the shortcut overlay visible while
    work is running.
    4. Confirm the overlay shows `tab to queue message` and `ctrl + c to
    interrupt`.
    5. Type a follow-up prompt during active work and press `Tab`; confirm
    it queues rather than submitting immediately.
    
    Targeted tests:
    
    - `just test -p codex-tui footer_snapshots`
    - `just test -p codex-tui footer_mode_snapshots`
    
    ## Validation Notes
    
    `just test -p codex-tui` currently has two unrelated guardian
    feature-flag test failures on this base:
    
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    
    `just argument-comment-lint codex-rs/tui/src/bottom_pane/footer.rs`
    could not run locally because the prebuilt wrapper requires `dotslash`;
    the touched Rust diff was manually inspected for opaque positional
    literals.
  • Move tool search metadata onto ToolExecutor (#25684)
    Deferred tools need to be searchable even when they are not implemented
    inside `codex-core`. Extension-provided tools can be registered for
    later discovery, but the search metadata path was still owned by
    core-specific runtime hooks, which meant the shared `ToolExecutor`
    abstraction could not describe how a deferred extension tool should
    appear in `tool_search`.
    
    ## Changes
    
    - Move `ToolSearchEntry` and `ToolSearchInfo` into `codex-tools` and
    re-export them from the shared tools crate.
    - Add a default `ToolExecutor::search_info` implementation that derives
    loadable tool-search metadata from function and namespace specs.
    - Forward search metadata through extension adapters and exposure
    overrides while keeping custom search text/source metadata for dynamic,
    MCP, and multi-agent tools.
    - Remove the old core-local `tool_search_entry` module now that search
    metadata lives with the shared executor APIs.
    
    ## Testing
    
    - Added `deferred_extension_tools_are_discoverable_with_tool_search`
    coverage in `core/src/tools/spec_plan_tests.rs`.
  • Fix stale TestAppServer rename in plugin_list test (#25705)
    ## Why
    
    #25701 renamed the app-server test harness to `TestAppServer`, but it
    raced with #25681, which added a new `plugin_list` test call site still
    using the old `McpProcess` name. Once both changes met on `main`,
    app-server test builds failed before running the suite because
    `McpProcess` no longer exists in that scope.
    
    This PR fixes that CI break by updating the remaining stale call site to
    the renamed helper.
    
    ## What Changed
    
    - Replaced the `McpProcess::new(...)` use in
    `codex-rs/app-server/tests/suite/v2/plugin_list.rs` with
    `TestAppServer::new(...)`.
    - Renamed the local variable from `mcp` to `app_server` at the same call
    site to match the helper rename.
    
    Relevant code:
    https://github.com/openai/codex/blob/aadd9c999b4e0789f7afb2b9b8cc43000bb47e86/codex-rs/app-server/tests/suite/v2/plugin_list.rs#L234-L246
    
    ## Verification
    
    Not run locally; this is a compile fix for the app-server test harness
    rename.
  • [codex] enable parallel standalone web search calls (#25702)
    ## Summary
    - opt the extension-backed standalone `web.run` tool into parallel tool
    execution
    - update the existing extension registration test to assert that the
    tool advertises parallel-call support
    
    ## Why
    The standalone web-search API endpoint now supports parallel requests.
    The extension executor still inherited the shared serial default,
    causing multiple `web.run` calls to acquire the exclusive runtime lock.
    
    ## Impact
    Models that emit multiple standalone web-search calls can now execute
    them concurrently when model-level parallel tool calls are enabled.
    
    ## Validation
    - `just fmt`
    - `just test -p codex-web-search-extension`
    - `git diff --check origin/main...HEAD`
  • fix: rename McpServer to TestAppServer (#25701)
    This PR brought to you via VS Code rather than Codex...
    
    - opened `codex-rs/app-server/tests/common/mcp_process.rs`
    - put the cursor on `McpServer`
    - hit `F2` and renamed the symbol to `TestAppServer`
    - went to the file tree
    - hit enter and renamed `mcp_process.rs` to `test_app_server.rs`
    - ran **Save All Files** from the Command Palette
    - ran `just fmt`
    
    The End
    
    (Admittedly, most of the local variables for `TestAppServer` are still
    named `mcp`, though.)
  • fix: Deduplicate installed local and remote curated plugins (#25681)
    ## Summary
    - Deduplicate installed `openai-curated` and `openai-curated-remote`
    plugin conflicts by feature flag.
    - Prefer remote when remote plugins are enabled; otherwise prefer local,
    while preserving one-sided installs.
    
    ## Testing
    - `just fmt`
    - `git diff --check`
    - Targeted `just test` was blocked locally because `cargo-nextest` is
    not installed.
  • [codex] Generalize deferred nested tool guidance (#25689)
    ## Summary
    - describe omitted code-mode tools as deferred nested tools instead of
    MCP/app tools
    - update the prompt-description assertion to match
    
    ## Why
    Deferred dynamic tools are also callable through `tools` and
    discoverable in `ALL_TOOLS`, so the previous MCP/app-specific wording
    was too narrow.
    
    ## Validation
    - `just fmt`
    - `just test -p codex-code-mode`
    - `git diff --check`