Commit Graph

1475 Commits

  • Update guardian output schema (#17061)
    ## Summary
    - Update guardian output schema to separate risk, authorization,
    outcome, and rationale.
    - Feed guardian rationale into rejection messages.
    - Split the guardian policy into template and tenant-config sections.
    
    ## Validation
    - `cargo test -p codex-core mcp_tool_call`
    - `env -u CODEX_SANDBOX_NETWORK_DISABLED INSTA_UPDATE=always cargo test
    -p codex-core guardian::`
    
    ---------
    
    Co-authored-by: Owen Lin <owen@openai.com>
  • Wire realtime WebRTC native media into Bazel (#17145)
    - Builds codex-realtime-webrtc through the normal Bazel Rust macro so
    native macOS WebRTC sources are included.\n- Shares the macOS -ObjC link
    flag with Bazel targets that can link libwebrtc.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Support remote exec cwd in TUI startup (#17142)
    When running with remote executor the cwd is the remote path. Today we
    check for existence of a local directory on startup and attempt to load
    config from it.
    
    For remote executors don't do that.
  • fix(debug-config, guardian): fix /debug-config rendering and guardian… (#17138)
    ## Description
    
    This PR fixes `/debug-config` so it shows more of the active
    requirements state, including reviewer requirements and managed feature
    pins. This made it clear that legacy MDM config was setting
    `approvals_reviewer = "guardian_subagent"` and that we were translating
    that into a requirements constraint.
    
    Also, translate `approvals_reviewer = "guardian_subagent"` (from legacy
    managed_config.toml) to `allowed_approvals_reviewers: guardian_subagent,
    user` instead of `allowed_approvals_reviewers: guardian_subagent`.
    
    Example `/debug-config`:
    ```
    Config layer stack (lowest precedence first):
      1. system (/etc/codex/config.toml) (enabled)
      2. user (/Users/owen/.codex/config.toml) (enabled)
      3. project (/Users/owen/repos/codex/.codex/config.toml) (enabled)
      4. legacy managed_config.toml (MDM) (enabled)
         MDM value:
           ...
    
           # Enable Guardian Mode
           features.guardian_approval = true
           approvals_reviewer = "guardian_subagent"
    
    Requirements:
      - allowed_approvals_reviewers: guardian_subagent, user (source: MDM managed_config.toml (legacy))
      - features: apps=true, plugins=true (source: cloud requirements)
    ```
    
    Before this PR, the `Requirements` section showed None.
  • Add WebRTC media transport to realtime TUI (#17058)
    Adds the `[realtime].transport = "webrtc"` TUI media path using a new
    `codex-realtime-webrtc` crate, while leaving app-server as the
    signaling/event source.\n\nLocal checks: fmt, diff-check, dependency
    tree only; test signal should come from CI.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [mcp] Support server-driven elicitations (#17043)
    - [x] Enables MCP elicitation for custom servers, not just Codex Apps
    - [x] Adds an RMCP service wrapper to preserve elicitation _meta
    - [x] Round-trips response _meta for persist/approval choices
    - [x] Updates TUI empty-schema elicitations into message-only approval
    prompts
  • Fix TUI crash when resuming the current thread (#17086)
    Problem: Resuming the live TUI thread through `/resume` could
    unsubscribe and reconnect the same app-server thread, leaving the UI
    crashed or disconnected.
    
    Solution: No-op `/resume` only when the selected thread is the currently
    attached active thread; keep the normal resume path for
    stale/displayed-only threads so recovery and reattach still work.
  • Show global AGENTS.md in /status (#17091)
    Addresses #3793
    
    Problem: /status only reported project-level AGENTS files, so sessions
    with a loaded global $CODEX_HOME/AGENTS.md still showed Agents.md as
    <none>.
    
    Solution: Track the global instructions file loaded during config
    initialization and prepend that path to the /status Agents.md summary,
    with coverage for AGENTS.md, AGENTS.override.md, and global-plus-project
    ordering.
  • feat: single app-server bootstrap in TUI (#16582)
    Before this, the TUI was starting 2 app-server. One to check the login
    status and one to actually start the session
    
    This PR make only one app-server startup and defer the login check in
    async, outside of the frame rendering path
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove expired April 2nd tooltip copy (#16698)
    Addresses #16677
    
    Problem: Paid-plan startup tooltips still advertised 2x rate limits
    until April 2nd after that promo had expired.
    
    Solution: Remove the stale expiry copy and use evergreen Codex App /
    Codex startup tips instead.
  • fix(tui): reduce startup and new-session latency (#17039)
    ## TL;DR
    
    - Fetches account/rateLimits/read asynchronously so the TUI can continue
    starting without waiting for the rate-limit response.
    - Fixes the /status card so it no longer leaves a stale “refreshing
    cached limits...” notice in terminal history.
    
    ## Problem
    
    The TUI bootstrap path fetched account rate limits synchronously
    (`account/rateLimits/read`) before the event loop started for
    ChatGPT/OpenAI-authenticated startups. This added ~670 ms of blocking
    latency in the measured hot-start case, even though rate-limit data is
    not needed to render the initial UI or accept user input. The delay was
    especially noticeable on hot starts where every other RPC
    (`account/read`, `model/list`, `thread/start`) completed in under 70 ms
    total.
    
    Moving that fetch to the background also exposed a `/status` UI bug: the
    status card is flattened into terminal scrollback when it is inserted. A
    transient "refreshing limits in background..." line could not be cleared
    later, because the async completion updated the retained `HistoryCell`,
    not the already-written terminal history.
    
    ## Mental model
    
    Before this change, `AppServerSession::bootstrap()` performed three
    sequential RPCs: `account/read` → `model/list` →
    `account/rateLimits/read`. The result of the third call was baked into
    `AppServerBootstrap` and applied to the chat widget before the event
    loop began.
    
    After this change, `bootstrap()` only performs two RPCs (`account/read`
    + `model/list`), and rate-limit fetching is kicked off as an async
    background task immediately after the first frame is scheduled. A new
    enum, `RateLimitRefreshOrigin`, tags each fetch so the event handler
    knows whether the result came from the startup prefetch or from a
    user-initiated `/status` command; they have different completion
    side-effects.
    
    The `get_login_status()` helper (used outside the main app flow) was
    also decoupled: it previously called the full `bootstrap()` just to
    check auth mode, wasting model-list and rate-limit work. It now calls
    the narrower `read_account()` directly.
    
    For `/status`, this PR keeps the background refresh request but stops
    printing transient refresh notices into status history when cached
    limits are already available. If a refresh updates the cache, the next
    `/status` command will render the new values.
    
    ## Non-goals
    
    - This change does not alter the rate-limit data itself.
    - This change does not introduce caching, retries, or staleness
    management for rate limits.
    - This change does not affect the `model/list` or `thread/start` RPCs;
    they remain on the critical startup path.
    
    ## Tradeoffs
    
    - **Stale-on-first-render**: The status bar will briefly show no
    rate-limit info until the background fetch completes; observed
    background fetches landed roughly in the 400-900 ms range after the UI
    appeared. This is acceptable because the user cannot meaningfully act on
    rate-limit data in the first fraction of a second.
    - **Error silence on startup prefetch**: If the startup prefetch fails,
    the error is logged but the UI is not notified (unlike `/status` refresh
    failures, which go through the status-command completion path). This
    avoids surfacing transient network errors as a startup blocker.
    - **Static `/status` history**: `/status` output is terminal history,
    not a live widget. The card now avoids progress-style language that
    would appear stuck in scrollback; users can run `/status` again to see
    newly cached values.
    - **`account_auth_mode` field removed from `AppServerBootstrap`**: The
    only consumer was `get_login_status()`, which no longer goes through
    `bootstrap()`. The field was dead weight.
    
    ## Architecture
    
    ### New types
    
    - `RateLimitRefreshOrigin` (in `app_event.rs`): A `Copy` enum
    distinguishing `StartupPrefetch` from `StatusCommand { request_id }`.
    Carried through `RefreshRateLimits` and `RateLimitsLoaded` events so the
    handler applies the right completion behavior.
    
    ### Modified types
    
    - `AppServerBootstrap`: Lost `account_auth_mode` and
    `rate_limit_snapshots`; gained `requires_openai_auth: bool` (passed
    through from the account response so the caller can decide whether to
    fire the prefetch).
    
    ### Control flow
    
    1. `bootstrap()` returns with `requires_openai_auth` and
    `has_chatgpt_account`.
    2. After scheduling the first frame, `App::run_inner` fires
    `refresh_rate_limits(StartupPrefetch)` if both flags are true.
    3. When `RateLimitsLoaded { StartupPrefetch, Ok(..) }` arrives,
    snapshots are applied and a frame is scheduled to repaint the status
    bar.
    4. When `RateLimitsLoaded { StartupPrefetch, Err(..) }` arrives, the
    error is logged and no UI update occurs.
    5. `/status`-initiated refreshes continue to use `StatusCommand {
    request_id }` and call `finish_status_rate_limit_refresh` on completion
    (success or failure).
    6. `/status` history cells with cached rate-limit rows no longer render
    an additional "refreshing limits" notice; the async refresh updates the
    cache for future status output.
    
    ### Extracted method
    
    - `AppServerSession::read_account()`: Factored out of `bootstrap()` so
    that `get_login_status()` can call it independently without triggering
    model-list or rate-limit work.
    
    ## Observability
    
    - The existing `tracing::warn!` for rate-limit fetch failures is
    preserved for the startup path.
    - No new metrics or spans are introduced. The startup-time improvement
    is observable via the existing `ready` timestamp in TUI startup logs.
    
    ## Tests
    
    - Existing tests in `status_command_tests.rs` are updated to match on
    `RateLimitRefreshOrigin::StatusCommand { request_id }` instead of a bare
    `request_id`.
    - Focused `/status` tests now assert that status history avoids
    transient refresh text, continues to request an async refresh, and uses
    refreshed cached limits in future status output.
    - No new tests are added for the startup prefetch path because it is a
    fire-and-forget spawn with no observable side-effect other than the
    widget state update, which is already covered by the
    snapshot-application tests.
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Use model metadata for Fast Mode status (#16949)
    Fast Mode status was still tied to one model name in the TUI and
    model-list plumbing. This changes the model metadata shape so a model
    can advertise additional speed tiers, carries that field through the
    app-server model list, and uses it to decide when to show Fast Mode
    status.
    
    For people using Codex, the behavior is intended to stay the same for
    existing models. Fast Mode still requires the existing signed-in /
    feature-gated path; the difference is that the UI can now recognize any
    model the model list marks as Fast-capable, instead of requiring a new
    client-side slug check.
  • Add WebRTC transport to realtime start (#16960)
    Adds WebRTC startup to the experimental app-server
    `thread/realtime/start` method with an optional transport enum. The
    websocket path remains the default; WebRTC offers create the realtime
    session through the shared start flow and emit the answer SDP via
    `thread/realtime/sdp`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Migrate apply_patch to executor filesystem (#17027)
    - Migrate apply-patch verification and application internals to use the
    async `ExecutorFileSystem` abstraction from `exec-server`.
    - Convert apply-patch `cwd` handling to `AbsolutePathBuf` through the
    verifier/parser/handler boundary.
    
    Doesn't change how the tool itself works.
  • [codex] Make AbsolutePathBuf joins infallible (#16981)
    Having to check for errors every time join is called is painful and
    unnecessary.
  • Fix missing resume hint on zero-token exits (#16987)
    Addresses #16421
    
    Problem: Resumed interactive sessions exited before new token usage
    skipped all footer lines, hiding the `codex resume` continuation
    command.
    
    It's not clear whether this was an intentional design choice, but I
    think it's reasonable to expect this message under these circumstances.
    
    Solution: Compose token usage and resume hints independently so
    resumable sessions still print the continuation command with zero usage.
  • [codex] reduce module visibility (#16978)
    ## Summary
    - reduce public module visibility across Rust crates, preferring private
    or crate-private modules with explicit crate-root public exports
    - update external call sites and tests to use the intended public crate
    APIs instead of reaching through module trees
    - add the module visibility guideline to AGENTS.md
    
    ## Validation
    - `cargo check --workspace --all-targets --message-format=short` passed
    before the final fix/format pass
    - `just fix` completed successfully
    - `just fmt` completed successfully
    - `git diff --check` passed
  • Make AGENTS.md discovery FS-aware (#15826)
    ## Summary
    - make AGENTS.md discovery and loading fully FS-aware and remove the
    non-FS discover helper
    - migrate remote-aware codex-core tests to use TestEnv workspace setup
    instead of syncing a local workspace copy
    - add AGENTS.md corner-case coverage, including directory fallbacks and
    remote-aware integration coverage
    
    ## Testing
    - cargo test -p codex-core project_doc -- --nocapture
    - cargo test -p codex-core hierarchical_agents -- --nocapture
    - cargo test -p codex-core agents_md -- --nocapture
    - cargo test -p codex-tui status -- --nocapture
    - cargo test -p codex-tui-app-server status -- --nocapture
    - just fix
    - just fmt
    - just bazel-lock-update
    - just bazel-lock-check
    - just argument-comment-lint
    - remote Linux executor tests in progress via scripts/test-remote-env.sh
  • [codex] Add danger-full-access denylist-only network mode (#16946)
    ## Summary
    
    This adds `experimental_network.danger_full_access_denylist_only` for
    orgs that want yolo / danger-full-access sessions to keep full network
    access while still enforcing centrally managed deny rules.
    
    When the flag is true and the session sandbox is `danger-full-access`,
    the network proxy starts with:
    
    - domain allowlist set to `*`
    - managed domain `deny` entries enforced
    - upstream proxy use allowed
    - all Unix sockets allowed
    - local/private binding allowed
    
    Caveat: the denylist is best effort only. In yolo / danger-full-access
    mode, Codex or the model can use an allowed socket or other
    local/private network path to bypass the proxy denylist, so this should
    not be treated as a hard security boundary.
    
    The flag is intentionally scoped to `SandboxPolicy::DangerFullAccess`.
    Read-only and workspace-write modes keep the existing managed/user
    allowlist, denylist, Unix socket, and local-binding behavior. This does
    not enable the non-loopback proxy listener setting; that still requires
    its own explicit config.
    
    This also threads the new field through config requirements parsing,
    app-server protocol/schema output, config API mapping, and the TUI debug
    config output.
    
    ## How to use
    
    Add the flag under `[experimental_network]` in the network policy config
    that is delivered to Codex. The setting is not under `[permissions]`.
    
    ```toml
    [experimental_network]
    enabled = true
    danger_full_access_denylist_only = true
    
    [experimental_network.domains]
    "blocked.example.com" = "deny"
    "*.blocked.example.com" = "deny"
    ```
    
    With that configuration, yolo / danger-full-access sessions get broad
    network access except for the managed denied domains above. The denylist
    remains a best-effort proxy policy because the session may still use
    allowed sockets to bypass it. Other sandbox modes do not get the
    wildcard domain allowlist or the socket/local-binding relaxations from
    this flag.
    
    ## Verification
    
    - `cargo test -p codex-config network_requirements`
    - `cargo test -p codex-core network_proxy_spec`
    - `cargo test -p codex-app-server map_requirements_toml_to_api`
    - `cargo test -p codex-tui debug_config_output`
    - `cargo test -p codex-app-server-protocol`
    - `just write-app-server-schema`
    - `just fmt`
    - `just fix -p codex-config -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-tui`
    - `just fix -p codex-core -p codex-config`
    - `git diff --check`
    - `cargo clean`
  • Refactor config types into a separate crate (#16962)
    Move config types into a separate crate because their macros expand into
    a lot of new code.
  • Speed up /mcp inventory listing (#16831)
    Addresses #16244
    
    This was a performance regression introduced when we moved the TUI on
    top of the app server API.
    
    Problem: `/mcp` rebuilt a full MCP inventory through
    `mcpServerStatus/list`, including resources and resource templates that
    made the TUI wait on slow inventory probes.
    
    Solution: add a lightweight `detail` mode to `mcpServerStatus/list`,
    have `/mcp` request tools-and-auth only, and cover the fast path with
    app-server and TUI tests.
    
    Testing: Confirmed slow (multi-second) response prior to change and
    immediate response after change.
    
    I considered two options:
    1. Change the existing `mcpServerStatus/list` API to accept an optional
    "details" parameter so callers can request only a subset of the
    information.
    2. Add a separate `mcpServer/list` API that returns only the servers,
    tools, and auth but omits the resources.
    
    I chose option 1, but option 2 is also a reasonable approach.
  • [codex-analytics] add protocol-native turn timestamps (#16638)
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16638).
    * #16870
    * #16706
    * #16659
    * #16641
    * #16640
    * __->__ #16638
  • tui: route device-code auth through app server (#16827)
    Addresses #7646
    Also enables device code auth for remote TUI sessions
    
    Problem: TUI onboarding handled device-code login directly rather than
    using the recently-added app server support for device auth. Also, auth
    screens kept animating while users needed to copy login details.
    
    Solution: Route device-code onboarding through app-server login APIs and
    make the auth screens static while those copy-oriented flows are
    visible.
  • feat(requirements): support allowed_approval_reviewers (#16701)
    ## Description
    
    Add requirements.toml support for `allowed_approvals_reviewers =
    ["user", "guardian_subagent"]`, so admins can now restrict the use of
    guardian mode.
    
    Note: If a user sets a reviewer that isn’t allowed by requirements.toml,
    config loading falls back to the first allowed reviewer and emits a
    startup warning.
    
    The table below describes the possible admin controls.
    | Admin intent | `requirements.toml` | User `config.toml` | End result |
    |---|---|---|---|
    | Leave Guardian optional | omit `allowed_approvals_reviewers` or set
    `["user", "guardian_subagent"]` | user chooses `approvals_reviewer =
    "user"` or `"guardian_subagent"` | Guardian off for `user`, on for
    `guardian_subagent` + `approval_policy = "on-request"` |
    | Force Guardian off | `allowed_approvals_reviewers = ["user"]` | any
    user value | Effective reviewer is `user`; Guardian off |
    | Force Guardian on | `allowed_approvals_reviewers =
    ["guardian_subagent"]` and usually `allowed_approval_policies =
    ["on-request"]` | any user reviewer value; user should also have
    `approval_policy = "on-request"` unless policy is forced | Effective
    reviewer is `guardian_subagent`; Guardian on when effective approval
    policy is `on-request` |
    | Allow both, but default to manual if user does nothing |
    `allowed_approvals_reviewers = ["user", "guardian_subagent"]` | omit
    `approvals_reviewer` | Effective reviewer is `user`; Guardian off |
    | Allow both, and user explicitly opts into Guardian |
    `allowed_approvals_reviewers = ["user", "guardian_subagent"]` |
    `approvals_reviewer = "guardian_subagent"` and `approval_policy =
    "on-request"` | Guardian on |
    | Invalid admin config | `allowed_approvals_reviewers = []` | anything |
    Config load error |
  • (tui): Decode percent-escaped bare local file links (#16810)
    Addresses #16622
    
    Problem: bare local file links in TUI markdown render percent-encoded
    path bytes literally, unlike file:// links.
    
    Solution: decode bare path targets before local-path expansion and add
    regression coverage for spaces and Unicode.
  • Annotate skill doc reads with skill names (#16813)
    Addresses #16303
    
    Problem: Skill doc reads render as plain `Read SKILL.md`, so the TUI
    hides which skill was opened.
    
    Solution: Best-effort annotate exact `SKILL.md` reads with the matching
    loaded skill name from `skills_all` before rendering exec cells.
    
    Before:
    ```
    • Explored
      └ Read SKILL.md
    ```
    
    After:
    ```
    • Explored
      └ Read SKILL.md (pr-babysitter skill)
    ```
  • Fix resume picker timestamp labels and stability (#16822)
    Problem: The resume picker used awkward "Created at" and "Updated at"
    headers, and its relative timestamps changed while navigating because
    they were recomputed on each redraw.
    
    Solution: Rename the headers to "Created" and "Updated", and anchor
    relative timestamp formatting to the picker load time so the displayed
    ages stay stable while browsing.
  • Fix TUI fast mode toggle regression (#16833)
    Addresses #16832
    
    Problem: After `/fast on`, the TUI omitted an explicit service-tier
    clear on later turns, so `/fast off` left app-server sessions stuck on
    `priority` until restart.
    
    Solution: Always submit the current service tier with user turns,
    including an explicit clear when Fast mode is off, and add a regression
    test for the `/fast on` -> `/fast off` flow.
  • Fix CJK word navigation in the TUI composer (#16829)
    Addresses #16584
    
    Problem: TUI word-wise cursor movement treated entire CJK runs as a
    single word, so Option/Alt+Left and Right skipped too far when editing
    East Asian text.
    
    Solution: Use Unicode word-boundary segments within each non-whitespace
    run so CJK text advances one segment at a time while preserving
    separator and delete-word behavior, and add regression coverage for CJK
    and mixed-script navigation.
    
    Testing: Manually tested solution by pasting text that includes CJK
    characters into the composer and confirmed that keyboard navigation
    worked correctly (after confirming it didn't prior to the change).
  • Codex/windows bazel rust test coverage no rs (#16528)
    # Why this PR exists
    
    This PR is trying to fix a coverage gap in the Windows Bazel Rust test
    lane.
    
    Before this change, the Windows `bazel test //...` job was nominally
    part of PR CI, but a non-trivial set of `//codex-rs/...` Rust test
    targets did not actually contribute test signal on Windows. In
    particular, targets such as `//codex-rs/core:core-unit-tests`,
    `//codex-rs/core:core-all-test`, and `//codex-rs/login:login-unit-tests`
    were incompatible during Bazel analysis on the Windows gnullvm platform,
    so they never reached test execution there. That is why the
    Cargo-powered Windows CI job could surface Windows-only failures that
    the Bazel-powered job did not report: Cargo was executing those tests,
    while Bazel was silently dropping them from the runnable target set.
    
    The main goal of this PR is to make the Windows Bazel test lane execute
    those Rust test targets instead of skipping them during analysis, while
    still preserving `windows-gnullvm` as the target configuration for the
    code under test. In other words: use an MSVC host/exec toolchain where
    Bazel helper binaries and build scripts need it, but continue compiling
    the actual crate targets with the Windows gnullvm cfgs that our current
    Bazel matrix is supposed to exercise.
    
    # Important scope note
    
    This branch intentionally removes the non-resource-loading `.rs` test
    and production-code changes from the earlier
    `codex/windows-bazel-rust-test-coverage` branch. The only Rust source
    changes kept here are runfiles/resource-loading fixes in TUI tests:
    
    - `codex-rs/tui/src/chatwidget/tests.rs`
    - `codex-rs/tui/tests/manager_dependency_regression.rs`
    
    That is deliberate. Since the corresponding tests already pass under
    Cargo, this PR is meant to test whether Bazel infrastructure/toolchain
    fixes alone are enough to get a healthy Windows Bazel test signal,
    without changing test behavior for Windows timing, shell output, or
    SQLite file-locking.
    
    # How this PR changes the Windows Bazel setup
    
    ## 1. Split Windows host/exec and target concerns in the Bazel test lane
    
    The core change is that the Windows Bazel test job now opts into an MSVC
    host platform for Bazel execution-time tools, but only for `bazel test`,
    not for the Bazel clippy build.
    
    Files:
    
    - `.github/workflows/bazel.yml`
    - `.github/scripts/run-bazel-ci.sh`
    - `MODULE.bazel`
    
    What changed:
    
    - `run-bazel-ci.sh` now accepts `--windows-msvc-host-platform`.
    - When that flag is present on Windows, the wrapper appends
    `--host_platform=//:local_windows_msvc` unless the caller already
    provided an explicit `--host_platform`.
    - `bazel.yml` passes that wrapper flag only for the Windows `bazel test
    //...` job.
    - The Bazel clippy job intentionally does **not** pass that flag, so
    clippy stays on the default Windows gnullvm host/exec path and continues
    linting against the target cfgs we care about.
    - `run-bazel-ci.sh` also now forwards `CODEX_JS_REPL_NODE_PATH` on
    Windows and normalizes the `node` executable path with `cygpath -w`, so
    tests that need Node resolve the runner's Node installation correctly
    under the Windows Bazel test environment.
    
    Why this helps:
    
    - The original incompatibility chain was mostly on the **exec/tool**
    side of the graph, not in the Rust test code itself. Moving host tools
    to MSVC lets Bazel resolve helper binaries and generators that were not
    viable on the gnullvm exec platform.
    - Keeping the target platform on gnullvm preserves cfg coverage for the
    crates under test, which is important because some Windows behavior
    differs between `msvc` and `gnullvm`.
    
    ## 2. Teach the repo's Bazel Rust macro about Windows link flags and
    integration-test knobs
    
    Files:
    
    - `defs.bzl`
    - `codex-rs/core/BUILD.bazel`
    - `codex-rs/otel/BUILD.bazel`
    - `codex-rs/tui/BUILD.bazel`
    
    What changed:
    
    - Replaced the old gnullvm-only linker flag block with
    `WINDOWS_RUSTC_LINK_FLAGS`, which now handles both Windows ABIs:
      - gnullvm gets `-C link-arg=-Wl,--stack,8388608`
    - MSVC gets `-C link-arg=/STACK:8388608`, `-C
    link-arg=/NODEFAULTLIB:libucrt.lib`, and `-C link-arg=ucrt.lib`
    - Threaded those Windows link flags into generated `rust_binary`,
    unit-test binaries, and integration-test binaries.
    - Extended `codex_rust_crate(...)` with:
      - `integration_test_args`
      - `integration_test_timeout`
    - Used those new knobs to:
    - mark `//codex-rs/core:core-all-test` as a long-running integration
    test
      - serialize `//codex-rs/otel:otel-all-test` with `--test-threads=1`
    - Added `src/**/*.rs` to `codex-rs/tui` test runfiles, because one
    regression test scans source files at runtime and Bazel does not expose
    source-tree directories unless they are declared as data.
    
    Why this helps:
    
    - Once host-side MSVC tools are available, we still need the generated
    Rust test binaries to link correctly on Windows. The MSVC-side
    stack/UCRT flags make those binaries behave more like their Cargo-built
    equivalents.
    - The integration-test macro knobs avoid hardcoding one-off test
    behavior in ad hoc BUILD rules and make the generated test targets more
    expressive where Bazel and Cargo have different runtime defaults.
    
    ## 3. Patch `rules_rs` / `rules_rust` so Windows MSVC exec-side Rust and
    build scripts are actually usable
    
    Files:
    
    - `MODULE.bazel`
    - `patches/rules_rs_windows_exec_linker.patch`
    - `patches/rules_rust_windows_bootstrap_process_wrapper_linker.patch`
    - `patches/rules_rust_windows_build_script_runner_paths.patch`
    - `patches/rules_rust_windows_exec_msvc_build_script_env.patch`
    - `patches/rules_rust_windows_msvc_direct_link_args.patch`
    - `patches/rules_rust_windows_process_wrapper_skip_temp_outputs.patch`
    - `patches/BUILD.bazel`
    
    What these patches do:
    
    - `rules_rs_windows_exec_linker.patch`
    - Adds a `rust-lld` filegroup for Windows Rust toolchain repos,
    symlinked to `lld-link.exe` from `PATH`.
      - Marks Windows toolchains as using a direct linker driver.
      - Supplies Windows stdlib link flags for both gnullvm and MSVC.
    - `rules_rust_windows_bootstrap_process_wrapper_linker.patch`
    - For Windows MSVC Rust targets, prefers the Rust toolchain linker over
    an inherited C++ linker path like `clang++`.
    - This specifically avoids the broken mixed-mode command line where
    rustc emits MSVC-style `/NOLOGO` / `/LIBPATH:` / `/OUT:` arguments but
    Bazel still invokes `clang++.exe`.
    - `rules_rust_windows_build_script_runner_paths.patch`
    - Normalizes forward-slash execroot-relative paths into Windows path
    separators before joining them on Windows.
    - Uses short Windows paths for `RUSTC`, `OUT_DIR`, and the build-script
    working directory to avoid path-length and quoting issues in third-party
    build scripts.
    - Exposes `RULES_RUST_BAZEL_BUILD_SCRIPT_RUNNER=1` to build scripts so
    crate-local patches can detect "this is running under Bazel's
    build-script runner".
    - Fixes the Windows runfiles cleanup filter so generated files with
    retained suffixes are actually retained.
    - `rules_rust_windows_exec_msvc_build_script_env.patch`
    - For exec-side Windows MSVC build scripts, stops force-injecting
    Bazel's `CC`, `CXX`, `LD`, `CFLAGS`, and `CXXFLAGS` when that would send
    GNU-flavored tool paths/flags into MSVC-oriented Cargo build scripts.
    - Rewrites or strips GNU-only `--sysroot`, MinGW include/library paths,
    stack-protector, and `_FORTIFY_SOURCE` flags on the MSVC exec path.
    - The practical effect is that build scripts can fall back to the Visual
    Studio toolchain environment already exported by CI instead of crashing
    inside Bazel's hermetic `clang.exe` setup.
    - `rules_rust_windows_msvc_direct_link_args.patch`
    - When using a direct linker on Windows, stops forwarding GNU driver
    flags such as `-L...` and `--sysroot=...` that `lld-link.exe` does not
    understand.
    - Passes non-`.lib` native artifacts as explicit `-Clink-arg=<path>`
    entries when needed.
    - Filters C++ runtime libraries to `.lib` artifacts on the Windows
    direct-driver path.
    - `rules_rust_windows_process_wrapper_skip_temp_outputs.patch`
    - Excludes transient `*.tmp*` and `*.rcgu.o` files from process-wrapper
    dependency search-path consolidation, so unstable compiler outputs do
    not get treated as real link search-path inputs.
    
    Why this helps:
    
    - The host-platform split alone was not enough. Once Bazel started
    analyzing/running previously incompatible Rust tests on Windows, the
    next failures were in toolchain plumbing:
    - MSVC-targeted Rust tests were being linked through `clang++` with
    MSVC-style arguments.
    - Cargo build scripts running under Bazel's Windows MSVC exec platform
    were handed Unix/GNU-flavored path and flag shapes.
    - Some generated paths were too long or had path-separator forms that
    third-party Windows build scripts did not tolerate.
    - These patches make that mixed Bazel/Cargo/Rust/MSVC path workable
    enough for the test lane to actually build and run the affected crates.
    
    ## 4. Patch third-party crate build scripts that were not robust under
    Bazel's Windows MSVC build-script path
    
    Files:
    
    - `MODULE.bazel`
    - `patches/aws-lc-sys_windows_msvc_prebuilt_nasm.patch`
    - `patches/ring_windows_msvc_include_dirs.patch`
    - `patches/zstd-sys_windows_msvc_include_dirs.patch`
    
    What changed:
    
    - `aws-lc-sys`
    - Detects Bazel's Windows MSVC build-script runner via
    `RULES_RUST_BAZEL_BUILD_SCRIPT_RUNNER` or a `bazel-out` manifest-dir
    path.
    - Uses `clang-cl` for Bazel Windows MSVC builds when no explicit
    `CC`/`CXX` is set.
    - Allows prebuilt NASM on the Bazel Windows MSVC path even when `nasm`
    is not available directly in the runner environment.
    - Avoids canonicalizing `CARGO_MANIFEST_DIR` in the Bazel Windows MSVC
    case, because that path may point into Bazel output/runfiles state where
    preserving the given path is more reliable than forcing a local
    filesystem canonicalization.
    - `ring`
    - Under the Bazel Windows MSVC build-script runner, copies the
    pregenerated source tree into `OUT_DIR` and uses that as the
    generated-source root.
    - Adds include paths needed by MSVC compilation for
    Fiat/curve25519/P-256 generated headers.
    - Rewrites a few relative includes in C sources so the added include
    directories are sufficient.
    - `zstd-sys`
    - Adds MSVC-only include directories for `compress`, `decompress`, and
    feature-gated dictionary/legacy/seekable sources.
    - Skips `-fvisibility=hidden` on MSVC targets, where that
    GCC/Clang-style flag is not the right mechanism.
    
    Why this helps:
    
    - After the `rules_rust` plumbing started running build scripts on the
    Windows MSVC exec path, some third-party crates still failed for
    crate-local reasons: wrong compiler choice, missing include directories,
    build-script assumptions about manifest paths, or Unix-only C compiler
    flags.
    - These crate patches address those crate-local assumptions so the
    larger toolchain change can actually reach first-party Rust test
    execution.
    
    ## 5. Keep the only `.rs` test changes to Bazel/Cargo runfiles parity
    
    Files:
    
    - `codex-rs/tui/src/chatwidget/tests.rs`
    - `codex-rs/tui/tests/manager_dependency_regression.rs`
    
    What changed:
    
    - Instead of asking `find_resource!` for a directory runfile like
    `src/chatwidget/snapshots` or `src`, these tests now resolve one known
    file runfile first and then walk to its parent directory.
    
    Why this helps:
    
    - Bazel runfiles are more reliable for explicitly declared files than
    for source-tree directories that happen to exist in a Cargo checkout.
    - This keeps the tests working under both Cargo and Bazel without
    changing their actual assertions.
    
    # What we tried before landing on this shape, and why those attempts did
    not work
    
    ## Attempt 1: Force `--host_platform=//:local_windows_msvc` for all
    Windows Bazel jobs
    
    This did make the previously incompatible test targets show up during
    analysis, but it also pushed the Bazel clippy job and some unrelated
    build actions onto the MSVC exec path.
    
    Why that was bad:
    
    - Windows clippy started running third-party Cargo build scripts with
    Bazel's MSVC exec settings and crashed in crates such as `tree-sitter`
    and `libsqlite3-sys`.
    - That was a regression in a job that was previously giving useful
    gnullvm-targeted lint signal.
    
    What this PR does instead:
    
    - The wrapper flag is opt-in, and `bazel.yml` uses it only for the
    Windows `bazel test` lane.
    - The clippy lane stays on the default Windows gnullvm host/exec
    configuration.
    
    ## Attempt 2: Broaden the `rules_rust` linker override to all Windows
    Rust actions
    
    This fixed the MSVC test-lane failure where normal `rust_test` targets
    were linked through `clang++` with MSVC-style arguments, but it broke
    the default gnullvm path.
    
    Why that was bad:
    
    -
    `@@rules_rs++rules_rust+rules_rust//util/process_wrapper:process_wrapper`
    on the gnullvm exec platform started linking with `lld-link.exe` and
    then failed to resolve MinGW-style libraries such as `-lkernel32`,
    `-luser32`, and `-lmingw32`.
    
    What this PR does instead:
    
    - The linker override is restricted to Windows MSVC targets only.
    - The gnullvm path keeps its original linker behavior, while MSVC uses
    the direct Windows linker.
    
    ## Attempt 3: Keep everything on pure Windows gnullvm and patch the V8 /
    Python incompatibility chain instead
    
    This would have preserved a single Windows ABI everywhere, but it is a
    much larger project than this PR.
    
    Why that was not the practical first step:
    
    - The original incompatibility chain ran through exec-side generators
    and helper tools, not only through crate code.
    - `third_party/v8` is already special-cased on Windows gnullvm because
    `rusty_v8` only publishes Windows prebuilts under MSVC names.
    - Fixing that path likely means deeper changes in
    V8/rules_python/rules_rust toolchain resolution and generator execution,
    not just one local CI flag.
    
    What this PR does instead:
    
    - Keep gnullvm for the target cfgs we want to exercise.
    - Move only the Windows test lane's host/exec platform to MSVC, then
    patch the build-script/linker boundary enough for that split
    configuration to work.
    
    ## Attempt 4: Validate compatibility with `bazel test --nobuild ...`
    
    This turned out to be a misleading local validation command.
    
    Why:
    
    - `bazel test --nobuild ...` can successfully analyze targets and then
    still exit 1 with "Couldn't start the build. Unable to run tests"
    because there are no runnable test actions after `--nobuild`.
    
    Better local check:
    
    ```powershell
    bazel build --nobuild --keep_going --host_platform=//:local_windows_msvc //codex-rs/login:login-unit-tests //codex-rs/core:core-unit-tests //codex-rs/core:core-all-test
    ```
    
    # Which patches probably deserve upstream follow-up
    
    My rough take is that the `rules_rs` / `rules_rust` patches are the
    highest-value upstream candidates, because they are fixing generic
    Windows host/exec + MSVC direct-linker behavior rather than
    Codex-specific test logic.
    
    Strong upstream candidates:
    
    - `patches/rules_rs_windows_exec_linker.patch`
    - `patches/rules_rust_windows_bootstrap_process_wrapper_linker.patch`
    - `patches/rules_rust_windows_build_script_runner_paths.patch`
    - `patches/rules_rust_windows_exec_msvc_build_script_env.patch`
    - `patches/rules_rust_windows_msvc_direct_link_args.patch`
    - `patches/rules_rust_windows_process_wrapper_skip_temp_outputs.patch`
    
    Why these seem upstreamable:
    
    - They address general-purpose problems in the Windows MSVC exec path:
      - missing direct-linker exposure for Rust toolchains
      - wrong linker selection when rustc emits MSVC-style args
    - Windows path normalization/short-path issues in the build-script
    runner
      - forwarding GNU-flavored CC/link flags into MSVC Cargo build scripts
      - unstable temp outputs polluting process-wrapper search-path state
    
    Potentially upstreamable crate patches, but likely with more care:
    
    - `patches/zstd-sys_windows_msvc_include_dirs.patch`
    - `patches/ring_windows_msvc_include_dirs.patch`
    - `patches/aws-lc-sys_windows_msvc_prebuilt_nasm.patch`
    
    Notes on those:
    
    - The `zstd-sys` and `ring` include-path fixes look fairly generic for
    MSVC/Bazel build-script environments and may be straightforward to
    propose upstream after we confirm CI stability.
    - The `aws-lc-sys` patch is useful, but it includes a Bazel-specific
    environment probe and CI-specific compiler fallback behavior. That
    probably needs a cleaner upstream-facing shape before sending it out, so
    upstream maintainers are not forced to adopt Codex's exact CI
    assumptions.
    
    Probably not worth upstreaming as-is:
    
    - The repo-local Starlark/test target changes in `defs.bzl`,
    `codex-rs/*/BUILD.bazel`, and `.github/scripts/run-bazel-ci.sh` are
    mostly Codex-specific policy and CI wiring, not generic rules changes.
    
    # Validation notes for reviewers
    
    On this branch, I ran the following local checks after dropping the
    non-resource-loading Rust edits:
    
    ```powershell
    cargo test -p codex-tui
    just --shell 'C:\Program Files\Git\bin\bash.exe' --shell-arg -lc -- fix -p codex-tui
    python .\tools\argument-comment-lint\run-prebuilt-linter.py -p codex-tui
    just --shell 'C:\Program Files\Git\bin\bash.exe' --shell-arg -lc fmt
    ```
    
    One local caveat:
    
    - `just argument-comment-lint` still fails on this Windows machine for
    an unrelated Bazel toolchain-resolution issue in
    `//codex-rs/exec:exec-all-test`, so I used the direct prebuilt linter
    for `codex-tui` as the local fallback.
    
    # Expected reviewer takeaway
    
    If this PR goes green, the important conclusion is that the Windows
    Bazel test coverage gap was primarily a Bazel host/exec toolchain
    problem, not a need to make the Rust tests themselves Windows-specific.
    That would be a strong signal that the deleted non-resource-loading Rust
    test edits from the earlier branch should stay out, and that future work
    should focus on upstreaming the generic `rules_rs` / `rules_rust`
    Windows fixes and reducing the crate-local patch surface.
  • Remove OPENAI_BASE_URL config fallback (#16720)
    The `OPENAI_BASE_URL` environment variable has been a significant
    support issue, so we decided to deprecate it in favor of an
    `openai_base_url` config key. We've had the deprecation warning in place
    for about a month, so users have had time to migrate to the new
    mechanism. This PR removes support for `OPENAI_BASE_URL` entirely.
  • fix(tui): sort skill mentions by display name first (#16710)
    ## Summary
    
    The skill list opened by '$' shows `interface.display_name` preferably
    if available but the sorting order of the search results use the
    `skill.name` for sorting the results regardless.
    
    This can be clearly seen in this example below: I expected with "pr" as
    the search term to have "PR Babysitter" be the first item, but instead
    it's way down the list.
    
    The reason is because "PR Babysitter" skill name is "babysit-pr" and
    therefore it doesn't rank as high as "pr-review-triage".
    
    This PR fixes this behavior.
    
    | Before | After |
    | --- | --- |
    | <img width="659" height="376" alt="image"
    src="https://github.com/user-attachments/assets/51a71491-62ec-4163-a6f3-943ddf55856d"
    /> | <img width="618" height="429" alt="image"
    src="https://github.com/user-attachments/assets/f5ec4f4a-c539-4a5d-bdc5-c3e3e630f530"
    /> |
    
    
    ## Testing
    
    - `just fmt`
    - `cargo test -p codex-tui
    bottom_pane::skill_popup::tests::display_name_match_sorting_beats_worse_secondary_search_term_matches
    --lib -- --exact`
    - `cargo test -p codex-tui`
  • Add remote --cd forwarding for app-server sessions (#16700)
    Addresses #16124
    
    Problem: `codex --remote --cd <path>` canonicalized the path locally and
    then omitted it from remote thread lifecycle requests, so remote-only
    working directories failed or were ignored.
    
    Solution: Keep remote startup on the local cwd, forward explicit `--cd`
    values verbatim to `thread/start`, `thread/resume`, and `thread/fork`,
    and cover the behavior with `codex-tui` tests.
    
    Testing: I manually tested `--remote --cd` with both absolute and
    relative paths and validated correct behavior.
    
    
    ---
    
    Update based on code review feedback:
    
    Problem: Remote `--cd` was forwarded to `thread/resume` and
    `thread/fork`, but not to `thread/list` lookups, so `--resume --last`
    and picker flows could select a session from the wrong cwd; relative cwd
    filters also failed against stored absolute paths.
    
    Solution: Apply explicit remote `--cd` to `thread/list` lookups for
    `--last` and picker flows, normalize relative cwd filters on the
    app-server before exact matching, and document/test the behavior.
  • Suppress bwrap warning when sandboxing is bypassed (#16667)
    Addresses #15282
    
    Problem: Codex warned about missing system bubblewrap even when
    sandboxing was disabled.
    
    Solution: Gate the bwrap warning on the active sandbox policy and skip
    it for danger-full-access and external-sandbox modes.
  • Fix stale /copy output after commentary-only turns (#16648)
    Addresses #16454
    
    Problem: `/copy` could keep stale output after a turn with
    commentary-only assistant text.
    
    Solution: Cache the latest non-empty agent message during a turn and
    promote it on turn completion.
  • remove temporary ownership re-exports (#16626)
    Stacked on #16508.
    
    This removes the temporary `codex-core` / `codex-login` re-export shims
    from the ownership split and rewrites callsites to import directly from
    `codex-model-provider-info`, `codex-models-manager`, `codex-api`,
    `codex-protocol`, `codex-feedback`, and `codex-response-debug-context`.
    
    No behavior change intended; this is the mechanical import cleanup layer
    split out from the ownership move.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • extract models manager and related ownership from core (#16508)
    ## Summary
    - split `models-manager` out of `core` and add `ModelsManagerConfig`
    plus `Config::to_models_manager_config()` so model metadata paths stop
    depending on `core::Config`
    - move login-owned/auth-owned code out of `core` into `codex-login`,
    move model provider config into `codex-model-provider-info`, move API
    bridge mapping into `codex-api`, move protocol-owned types/impls into
    `codex-protocol`, and move response debug helpers into a dedicated
    `response-debug-context` crate
    - move feedback tag emission into `codex-feedback`, relocate tests to
    the crates that now own the code, and keep broad temporary re-exports so
    this PR avoids a giant import-only rewrite
    
    ## Major moves and decisions
    - created `codex-models-manager` as the owner for model
    cache/catalog/config/model info logic, including the new
    `ModelsManagerConfig` struct
    - created `codex-model-provider-info` as the owner for provider config
    parsing/defaults and kept temporary `codex-login`/`codex-core`
    re-exports for old import paths
    - moved `api_bridge` error mapping + `CoreAuthProvider` into
    `codex-api`, while `codex-login::api_bridge` temporarily re-exports
    those symbols and keeps the `auth_provider_from_auth` wrapper
    - moved `auth_env_telemetry` and `provider_auth` ownership to
    `codex-login`
    - moved `CodexErr` ownership to `codex-protocol::error`, plus
    `StreamOutput`, `bytes_to_string_smart`, and network policy helpers to
    protocol-owned modules
    - created `codex-response-debug-context` for
    `extract_response_debug_context`, `telemetry_transport_error_message`,
    and related response-debug plumbing instead of leaving that behavior in
    `core`
    - moved `FeedbackRequestTags`, `emit_feedback_request_tags`, and
    `emit_feedback_request_tags_with_auth_env` to `codex-feedback`
    - deferred removal of temporary re-exports and the mechanical import
    rewrites to a stacked follow-up PR so this PR stays reviewable
    
    ## Test moves
    - moved auth refresh coverage from `core/tests/suite/auth_refresh.rs` to
    `login/tests/suite/auth_refresh.rs`
    - moved text encoding coverage from
    `core/tests/suite/text_encoding_fix.rs` to
    `protocol/src/exec_output_tests.rs`
    - moved model info override coverage from
    `core/tests/suite/model_info_overrides.rs` to
    `models-manager/src/model_info_overrides_tests.rs`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: address unused variable on windows (#16633)
    This slipped in during https://github.com/openai/codex/pull/16578. I am
    still working on getting Windows working properly with Bazel on PRs.
  • fix(tui): handle zellij redraw and composer rendering (#16578)
    ## TL;DR
    
    Fixes the issues when using Codex CLI with Zellij multiplexer. Before
    this PR there would be no scrollback when using it inside a zellij
    terminal.
    
    ## Problem
    
    Addresses #2558
    
    Zellij does not support ANSI scroll-region manipulation (`DECSTBM` /
    Reverse Index) or the alternate screen buffer in the way traditional
    terminals do. When codex's TUI runs inside Zellij, two things break: (1)
    inline history insertion corrupts the display because the scroll-region
    escape sequences are silently dropped or mishandled, and (2) the
    composer textarea renders with inherited background/foreground styles
    that produce unreadable text against Zellij's pane chrome.
    
    ## Mental model
    
    The fix introduces a **Zellij mode** — a runtime boolean detected once
    at startup via `codex_terminal_detection::terminal_info().is_zellij()` —
    that gates two subsystems onto Zellij-safe terminal strategies:
    
    - **History insertion** (`insert_history.rs`): Instead of using
    `DECSTBM` scroll regions and Reverse Index (`ESC M`) to slide content
    above the viewport, Zellij mode scrolls the screen by emitting `\n` at
    the bottom row and then writes history lines at absolute positions. This
    avoids every escape sequence Zellij mishandles.
    - **Viewport expansion** (`tui.rs`): When the viewport grows taller than
    available space, the standard path uses `scroll_region_up` on the
    backend. Zellij mode instead emits newlines at the screen bottom to push
    content up, then invalidates the ratatui diff buffer so the next draw is
    a full repaint.
    - **Composer rendering** (`chat_composer.rs`, `textarea.rs`): All text
    rendering in the input area uses an explicit `base_style` with
    `Color::Reset` foreground, preventing Zellij's pane styling from
    bleeding into the textarea. The prompt chevron (`›`) and placeholder
    text use explicit color constants instead of relying on `.bold()` /
    `.dim()` modifiers that render inconsistently under Zellij.
    
    ## Non-goals
    
    - This change does not fix or improve Zellij's terminal emulation
    itself.
    - It does not rearchitect the inline viewport model; it adds a parallel
    code path gated on detection.
    - It does not touch the alternate-screen disable logic (that already
    existed and continues to use `is_zellij` via the same detection).
    
    ## Tradeoffs
    
    - **Code duplication in `insert_history.rs`**: The Zellij and Standard
    branches share the line-rendering loop (color setup, span merging,
    `write_spans`) but differ in the scrolling preamble. The duplication is
    intentional — merging them would force a complex conditional state
    machine that's harder to reason about than two flat sequences.
    - **`invalidate_viewport` after every Zellij history flush or viewport
    expansion**: This forces a full repaint on every draw cycle in Zellij,
    which is more expensive than ratatui's normal diff-based rendering. This
    is necessary because Zellij's lack of scroll-region support means the
    diff buffer's assumptions about what's on screen are invalid after we
    manually move content.
    - **Explicit colors vs semantic modifiers**: Replacing `.bold()` /
    `.dim()` with `Color::Cyan` / `Color::DarkGray` / `Color::White` in the
    Zellij branch sacrifices theme-awareness for correctness. If the project
    ever adopts a theming system, Zellij styling will need to participate.
    
    ## Architecture
    
    The Zellij detection flag flows through three layers:
    
    1. **`codex_terminal_detection`** — `TerminalInfo::is_zellij()` (new
    convenience method) reads the already-detected `Multiplexer` variant.
    2. **`Tui` struct** — caches `is_zellij` at construction; passes it into
    `update_inline_viewport`, `flush_pending_history_lines`, and
    `insert_history_lines_with_mode`.
    3. **`ChatComposer` struct** — independently caches `is_zellij` at
    construction; uses it in `render_textarea` for style decisions.
    
    The two caches (`Tui.is_zellij` and `ChatComposer.is_zellij`) are read
    from the same global `OnceLock<TerminalInfo>`, so they always agree.
    
    ## Observability
    
    No new logging, metrics, or tracing is introduced. Diagnosis depends on:
    - Whether `ZELLIJ` or `ZELLIJ_SESSION_NAME` env vars are set (the
    detection heuristic).
    - Visual inspection of the rendered TUI inside Zellij vs a standard
    terminal.
    - The insta snapshot `zellij_empty_composer` captures the Zellij-mode
    render path.
    
    ## Tests
    
    - `terminal_info_reports_is_zellij` — unit test in `terminal-detection`
    confirming the convenience method.
    - `zellij_empty_composer_snapshot` — insta snapshot in `chat_composer`
    validating the Zellij render path for an empty composer.
    - `vt100_zellij_mode_inserts_history_and_updates_viewport` — integration
    test in `insert_history` verifying that Zellij-mode history insertion
    writes content and shifts the viewport.
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Fix fork source display in /status (expose forked_from_id in app server) (#16596)
    Addresses #16560
    
    Problem: `/status` stopped showing the source thread id in forked TUI
    sessions after the app-server migration.
    
    Solution: Carry fork source ids through app-server v2 thread data and
    the TUI session adapter, and update TUI fixtures so `/status` matches
    the old TUI behavior.
  • Fix stale turn steering during TUI review follow-ups (#16588)
    Addresses #16389
    
    Problem: `/review` follow-ups can crash when app-server TUI steers with
    a stale active turn id; #14717 introduced the client-side race, and
    #15714 only handled the “no active turn” half.
    
    Solution: Treat turn-id mismatch as stale cached state too, sync to the
    server’s current turn id, retry once, and let review turns fall into the
    existing queue path.
  • Fix resume picker stale thread names (#16601)
    Addresses #16562
    
    Problem: Resume picker could keep a stale backend-provided thread title
    instead of the latest name from session_index.jsonl.
    
    Solution: Always backfill/override picker row names from local
    session_index.jsonl and cover stale-name replacement with a regression
    test.
  • Fix resume picker initial loading state (#16591)
    Addresses #16514
    
    Problem: Resume picker could show “No sessions yet” before the initial
    session fetch finished.
    
    Solution: Render a loading message while the first page is pending, and
    keep the empty state for truly empty results.
  • [codex] Remove codex-core config type shim (#16529)
    ## Why
    
    This finishes the config-type move out of `codex-core` by removing the
    temporary compatibility shim in `codex_core::config::types`. Callers now
    depend on `codex-config` directly, which keeps these config model types
    owned by the config crate instead of re-expanding `codex-core` as a
    transitive API surface.
    
    ## What Changed
    
    - Removed the `codex-rs/core/src/config/types.rs` re-export shim and the
    `core::config::ApprovalsReviewer` re-export.
    - Updated `codex-core`, `codex-cli`, `codex-tui`, `codex-app-server`,
    `codex-mcp-server`, and `codex-linux-sandbox` call sites to import
    `codex_config::types` directly.
    - Added explicit `codex-config` dependencies to downstream crates that
    previously relied on the `codex-core` re-export.
    - Regenerated `codex-rs/core/config.schema.json` after updating the
    config docs path reference.
  • core: remove cross-crate re-exports from lib.rs (#16512)
    ## Why
    
    `codex-core` was re-exporting APIs owned by sibling `codex-*` crates,
    which made downstream crates depend on `codex-core` as a proxy module
    instead of the actual owner crate.
    
    Removing those forwards makes crate boundaries explicit and lets leaf
    crates drop unnecessary `codex-core` dependencies. In this PR, this
    reduces the dependency on `codex-core` to `codex-login` in the following
    files:
    
    ```
    codex-rs/backend-client/Cargo.toml
    codex-rs/mcp-server/tests/common/Cargo.toml
    ```
    
    ## What
    
    - Remove `codex-rs/core/src/lib.rs` re-exports for symbols owned by
    `codex-login`, `codex-mcp`, `codex-rollout`, `codex-analytics`,
    `codex-protocol`, `codex-shell-command`, `codex-sandboxing`,
    `codex-tools`, and `codex-utils-path`.
    - Delete the `default_client` forwarding shim in `codex-rs/core`.
    - Update in-crate and downstream callsites to import directly from the
    owning `codex-*` crate.
    - Add direct Cargo dependencies where callsites now target the owner
    crate, and remove `codex-core` from `codex-rs/backend-client`.
  • core: use codex-mcp APIs directly (#16510)
    ## Why
    
    `codex-mcp` already owns the shared MCP API surface, including `auth`,
    `McpConfig`, `CODEX_APPS_MCP_SERVER_NAME`, and tool-name helpers in
    [`codex-rs/codex-mcp/src/mcp/mod.rs`](https://github.com/openai/codex/blob/f61e85dbfb5373cde6827d232ac8ea447c237e81/codex-rs/codex-mcp/src/mcp/mod.rs#L1-L35).
    Re-exporting that surface from `codex_core::mcp` gives downstream crates
    two import paths for the same API and hides the real crate dependency.
    
    This PR keeps `codex_core::mcp` focused on the local `McpManager`
    wrapper in
    [`codex-rs/core/src/mcp.rs`](https://github.com/openai/codex/blob/f61e85dbfb5373cde6827d232ac8ea447c237e81/codex-rs/core/src/mcp.rs#L13-L40)
    and makes consumers import shared MCP APIs from `codex_mcp` directly.
    
    ## What
    
    - Remove the `codex_mcp::mcp` re-export surface from `core/src/mcp.rs`.
    - Update `codex-core` internals plus `codex-app-server`, `codex-cli`,
    and `codex-tui` test code to import MCP APIs from `codex_mcp::mcp`
    directly.
    - Add explicit `codex-mcp` dependencies where those crates now use that
    API surface, and refresh `Cargo.lock`.
    
    ## Verification
    
    - `just bazel-lock-check`
    - `cargo test -p codex-core -p codex-cli -p codex-tui`
      - `codex-cli` passed.
    - `codex-core` still fails five unrelated config tests in
    `core/src/config/config_tests.rs` (`approvals_reviewer_*` and
    `smart_approvals_alias_*`).
    - A broader `cargo test -p codex-core -p codex-app-server -p codex-cli
    -p codex-tui` run previously hung in `codex-app-server` test
    `in_process_start_uses_requested_session_source_for_thread_start`.
  • Fix paste-driven bottom pane completion teardown (#16202)
    Fix paste-driven bottom-pane completion teardown (#16192)
    
    `BottomPane::handle_paste()` could leave a completed modal flow mounted
    while re-enabling the composer, putting the TUI in an inconsistent state
    where stale views could still affect rendering and input routing. Align
    the paste path with the existing key-driven completion logic by tearing
    down the active modal flow before restoring composer input, and add a
    regression test covering the stacked-view case that exposed the bug.
    
    Big thanks to @iqdoctor for identifying the root cause for this issue.
  • Fix TUI app-server permission profile conversions (#16284)
    Addresses #16283
    
    Problem: TUI app-server permission approvals could drop filesystem
    grants because request and response payloads were round-tripped through
    mismatched camelCase and snake_case JSON shapes.
    Solution: Replace the lossy JSON round-trips with typed app-server/core
    permission conversions so requested and granted permission profiles,
    including filesystem paths and scope, are preserved end to end.
  • Fix regression: "not available in TUI" error message (#16273)
    Addresses a recent TUI regression
    
    Problem: Pressing Ctrl+C during early TUI startup could route an
    interrupt with no active turn into the generic unsupported-op fallback,
    showing “Not available in app-server TUI yet for thread …” repeatedly.
    
    Solution: Treat interrupt requests as handled when no active turn exists
    yet, preventing fallback error spam during startup, and add a regression
    test covering interrupt-without-active-turn behavior.
  • fix(guardian): make GuardianAssessmentEvent.action strongly typed (#16448)
    ## Description
    
    Previously the `action` field on `EventMsg::GuardianAssessment`, which
    describes what Guardian is reviewing, was typed as an arbitrary JSON
    blob. This PR cleans it up and defines a sum type representing all the
    various actions that Guardian can review.
    
    This is a breaking change (on purpose), which is fine because:
    - the Codex app / VSCE does not actually use `action` at the moment
    - the TUI code that consumes `action` is updated in this PR as well
    - rollout files that serialized old `EventMsg::GuardianAssessment` will
    just silently drop these guardian events
    - the contract is defined as unstable, so other clients have a fair
    warning :)
    
    This will make things much easier for followup Guardian work.
    
    ## Why
    
    The old guardian review payloads worked, but they pushed too much shape
    knowledge into downstream consumers. The TUI had custom JSON parsing
    logic for commands, patches, network requests, and MCP calls, and the
    app-server protocol was effectively just passing through an opaque blob.
    
    Typing this at the protocol boundary makes the contract clearer.