Commit Graph

1456 Commits

  • Option to Notify Workspace Owner When Usage Limit is Reached (#16969)
    ## Summary
    - Replace the manual `/notify-owner` flow with an inline confirmation
    prompt when a usage-based workspace member hits a credits-depleted
    limit.
    - Fetch the current workspace role from the live ChatGPT
    `accounts/check/v4-2023-04-27` endpoint so owner/member behavior matches
    the desktop and web clients.
    - Keep owner, member, and spend-cap messaging distinct so we only offer
    the owner nudge when the workspace is actually out of credits.
    
    ## What Changed
    - `backend-client`
    - Added a typed fetch for the current account role from
    `accounts/check`.
      - Mapped backend role values into a Rust workspace-role enum.
    - `app-server` and protocol
      - Added `workspaceRole` to `account/read` and `account/updated`.
    - Derived `isWorkspaceOwner` from the live role, with a fallback to the
    cached token claim when the role fetch is unavailable.
    - `tui`
      - Removed the explicit `/notify-owner` slash command.
    - When a member is blocked because the workspace is out of credits, the
    error now prompts:
    - `Your workspace is out of credits. Request more from your workspace
    owner? [y/N]`
      - Choosing `y` sends the existing owner-notification request.
    - Choosing `n`, pressing `Esc`, or accepting the default selection
    dismisses the prompt without sending anything.
    - Selection popups now honor explicit item shortcuts, which is how the
    `y` / `n` interaction is wired.
    
    ## Reviewer Notes
    - The main behavior change is scoped to usage-based workspace members
    whose workspace credits are depleted.
    - Spend-cap reached should not show the owner-notification prompt.
    - Owners and admins should continue to see `/usage` guidance instead of
    the member prompt.
    - The live role fetch is best-effort; if it fails, we fall back to the
    existing token-derived ownership signal.
    
    ## Testing
    - Manual verification
      - Workspace owner does not see the member prompt.
    - Workspace member with depleted credits sees the confirmation prompt
    and can send the nudge with `y`.
    - Workspace member with spend cap reached does not see the
    owner-notification prompt.
    
    ### Workspace member out of usage
    
    https://github.com/user-attachments/assets/341ac396-eff4-4a7f-bf0c-60660becbea1
    
    ### Workspace owner
    <img width="1728" height="1086" alt="Screenshot 2026-04-09 at 11 48
    22 AM"
    src="https://github.com/user-attachments/assets/06262a45-e3fc-4cc4-8326-1cbedad46ed6"
    />
  • feat(tui): Ctrl+O copy hotkey and harden copy-as-markdown behavior (#16966)
    ## TL;DR
    
    - New `Ctrl+O` shortcut on top of the existing `/copy` command, allowing
    users to copy the latest agent response without having to cancel a plan
    or type `/copy`
    - Copy server clipboard to the client over SSH (OSC 52)
    - Fixes linux copy behavior: a clipboard handle has to be kept alive
    while the paste happens for the contents to be preserved
    - Uses arboard as primary mechanism on Windows, falling back to
    PowerShell copy clipboard function
    - Works with resumes, rolling back during a session, etc.
    
    Tested on macOS, Linux/X11, Windows WSL2, Windows cmd.exe, Windows
    PowerShell, Windows VSCode PowerShell, Windows VSCode WSL2, SSH (macOS
    -> macOS).
    
    ## Problem
    
    The TUI's `/copy` command was fragile. It relied on a single
    `last_copyable_output` field that was bluntly cleared on every rollback
    and thread reconfiguration, making copied content unavailable after
    common operations like backtracking. It also had no keyboard shortcut,
    requiring users to type `/copy` each time. The previous clipboard
    backend mixed platform selection policy with low-level I/O in a way that
    was hard to test, and it did not keep the Linux clipboard owner alive —
    meaning pasted content could vanish once the process that wrote it
    dropped its `arboard::Clipboard`.
    
    This addresses the text-copy failure modes reported in #12836, #15452,
    and #15663: native Linux clipboard access failing in remote or
    unreachable-display environments, copy state going blank even after
    visible assistant output, and local Linux X11 reporting success while
    leaving the clipboard empty.
    
    ## Shortcut rationale
    
    The copy hotkey is `Ctrl+O` rather than `Alt+C` because Alt/Option
    combinations are not delivered consistently by macOS terminal emulators.
    Terminal.app and iTerm2 can treat Option as text input or as a
    configurable Meta/Esc prefix, and Option+C may be consumed or
    transformed before the TUI sees an `Alt+C` key event. `Ctrl+O` is a
    stable control-key chord in Terminal.app, iTerm2, SSH, and the existing
    cross-platform terminal stack.
    
    ## Mental model
    
    Agent responses are now tracked as a bounded, ordinal-indexed history
    (`agent_turn_markdowns: Vec<AgentTurnMarkdown>`) rather than a single
    nullable string. Each completed agent turn appends an entry keyed by its
    ordinal (the number of user turns seen so far). Rollbacks pop entries
    whose ordinal exceeds the remaining turn count, then use the visible
    transcript cells as a best-effort fallback if the ordinal history no
    longer has a surviving entry. This means `/copy` and `Ctrl+O` reflect
    the most recent surviving agent response after a backtrack, instead of
    going blank.
    
    The clipboard backend was rewritten as `clipboard_copy.rs` with a
    strategy-injection design: `copy_to_clipboard_with` accepts closures for
    the OSC 52, arboard, and WSL PowerShell paths, making the selection
    logic fully unit-testable without touching real clipboards. On Linux,
    the `Clipboard` handle is returned as a `ClipboardLease` stored on
    `ChatWidget`, keeping X11/Wayland clipboard ownership alive for the
    lifetime of the TUI. When native copy fails under WSL, the backend now
    tries the Windows clipboard through PowerShell before falling back to
    OSC 52.
    
    ## Non-goals
    
    - This change does not introduce rich-text (HTML) clipboard support; the
    copied content is raw markdown.
    - It does not add a paste-from-history picker or multi-entry clipboard
    ring.
    - WSL support remains a best-effort fallback, not a new configuration
    surface or guarantee for every terminal/host combination.
    
    ## Tradeoffs
    
    - **Bounded history (256 entries)**: `MAX_AGENT_COPY_HISTORY` caps
    memory. For sessions with thousands of turns this silently drops the
    oldest entries. The cap is generous enough for realistic sessions.
    - **`saw_copy_source_this_turn` flag**: Prevents double-recording when
    both `AgentMessage` and `TurnComplete.last_agent_message` fire for the
    same turn. The flag is reset on turn start and on turn complete,
    creating a narrow window where a race between the two events could
    theoretically skip recording. In practice the protocol delivers them
    sequentially.
    - **Transcript fallback on rollback**:
    `last_agent_markdown_from_transcript` walks the visible transcript cells
    to reconstruct plain text when the ordinal history has been fully
    truncated. This path uses `AgentMessageCell::plain_text()` which joins
    rendered spans, so it reconstructs display text rather than the original
    raw markdown. It keeps visible text copyable after rollback, but
    responses with markdown-specific syntax can diverge from the original
    source.
    - **Clipboard fallback ordering**: SSH still uses OSC 52 exclusively
    because native/PowerShell clipboard access would target the wrong
    machine. Local sessions try native clipboard first, then WSL PowerShell
    when running under WSL, then OSC 52. This adds one process-spawn
    fallback for WSL users but keeps the normal desktop and SSH paths
    simple.
    
    ## Architecture
    
    ```
    chatwidget.rs
    ├── agent_turn_markdowns: Vec<AgentTurnMarkdown>  // ordinal-indexed history
    ├── last_agent_markdown: Option<String>            // always == last entry's markdown
    ├── completed_turn_count: usize                    // incremented when user turns enter history
    ├── saw_copy_source_this_turn: bool                // dedup guard
    ├── clipboard_lease: Option<ClipboardLease>        // keeps Linux clipboard owner alive
    │
    ├── record_agent_markdown(&str)                    // append/update history entry
    ├── truncate_agent_turn_markdowns_to_turn_count()  // rollback support
    ├── copy_last_agent_markdown()                     // public entry point (slash + hotkey)
    └── copy_last_agent_markdown_with(fn)              // testable core
    
    clipboard_copy.rs
    ├── copy_to_clipboard(text) -> Result<Option<ClipboardLease>>
    ├── copy_to_clipboard_with(text, ssh, wsl, osc52_fn, arboard_fn, wsl_fn)
    ├── ClipboardLease { _clipboard on linux }
    ├── arboard_copy(text)          // platform-conditional native clipboard path
    ├── wsl_clipboard_copy(text)    // WSL PowerShell fallback
    ├── osc52_copy(text)            // /dev/tty -> stdout fallback
    ├── SuppressStderr              // macOS stderr redirect guard
    ├── is_ssh_session()
    └── is_wsl_session()
    
    app_backtrack.rs
    ├── last_agent_markdown_from_transcript()  // reconstruct from visible cells
    └── truncate call sites in trim/apply_confirmed_rollback
    ```
    
    ## Observability
    
    - `tracing::warn!` on native clipboard failure before OSC 52 fallback.
    - `tracing::debug!` on `/dev/tty` open/write failure before stdout
    fallback.
    - History cell messages: "Copied last message to clipboard", "Copy
    failed: {error}", "No agent response to copy" appear in the TUI
    transcript.
    
    ## Tests
    
    - `clipboard_copy.rs`: Unit tests cover OSC 52 encoding roundtrip,
    payload size rejection, writer output, SSH-only OSC52 routing, non-WSL
    native-to-OSC52 fallback, WSL native-to-PowerShell fallback, WSL
    PowerShell-to-OSC52 fallback, and all-error reporting via strategy
    injection.
    - `chatwidget/tests/slash_commands.rs`: Updated existing `/copy` tests
    to use `last_agent_markdown_text()` accessor. Added coverage for the
    Linux clipboard lease lifecycle, missing
    `TurnComplete.last_agent_message` fallback through completed assistant
    items, replayed legacy agent messages, stale-output prevention after
    rollback, and the `Ctrl+O` no-output hotkey path.
    - `app_backtrack.rs`: Added
    `agent_group_count_ignores_context_compacted_marker` verifying that
    info-event cells don't inflate the agent group count.
    
    ---------
    
    Co-authored-by: Felipe Coury <felipe.coury@gmail.com>
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Forward app-server turn clientMetadata to Responses (#16009)
    ## Summary
    App-server v2 already receives turn-scoped `clientMetadata`, but the
    Rust app-server was dropping it before the outbound Responses request.
    This change keeps the fix lightweight by threading that metadata through
    the existing turn-metadata path rather than inventing a new transport.
    
    ## What we're trying to do and why
    We want turn-scoped metadata from the app-server protocol layer,
    especially fields like Hermes/GAAS run IDs, to survive all the way to
    the actual Responses API request so it is visible in downstream
    websocket request logging and analytics.
    
    The specific bug was:
    - app-server protocol uses camelCase `clientMetadata`
    - Responses transport already has an existing turn metadata carrier:
    `x-codex-turn-metadata`
    - websocket transport already rewrites that header into
    `request.request_body.client_metadata["x-codex-turn-metadata"]`
    - but the Rust app-server never parsed or stored `clientMetadata`, so
    nothing from the app-server request was making it into that existing
    path
    
    This PR fixes that without adding a new header or a second metadata
    channel.
    
    ## How we did it
    ### Protocol surface
    - Add optional `clientMetadata` to v2 `TurnStartParams` and
    `TurnSteerParams`
    - Regenerate the JSON schema / TypeScript fixtures
    - Update app-server docs to describe the field and its behavior
    
    ### Runtime plumbing
    - Add a dedicated core op for app-server user input carrying turn-scoped
    metadata: `Op::UserInputWithClientMetadata`
    - Wire `turn/start` and `turn/steer` through that op / signature path
    instead of dropping the metadata at the message-processor boundary
    - Store the metadata in `TurnMetadataState`
    
    ### Transport behavior
    - Reuse the existing serialized `x-codex-turn-metadata` payload
    - Merge the new app-server `clientMetadata` into that JSON additively
    - Do **not** replace built-in reserved fields already present in the
    turn metadata payload
    - Keep websocket behavior unchanged at the outer shape level: it still
    sends only `client_metadata["x-codex-turn-metadata"]`, but that JSON
    string now contains the merged fields
    - Keep HTTP fallback behavior unchanged except that the existing
    `x-codex-turn-metadata` header now includes the merged fields too
    
    ### Request shape before / after
    Before, a websocket `response.create` looked like:
    ```json
    {
      "type": "response.create",
      "client_metadata": {
        "x-codex-turn-metadata": "{\"session_id\":\"...\",\"turn_id\":\"...\"}"
      }
    }
    ```
    Even if the app-server caller supplied `clientMetadata`, it was not
    represented there.
    
    After, the same request shape is preserved, but the serialized payload
    now includes the new turn-scoped fields:
    ```json
    {
      "type": "response.create",
      "client_metadata": {
        "x-codex-turn-metadata": "{\"session_id\":\"...\",\"turn_id\":\"...\",\"fiber_run_id\":\"fiber-start-123\",\"origin\":\"gaas\"}"
      }
    }
    ```
    
    ## Validation
    ### Targeted tests added / updated
    - protocol round-trip coverage for `clientMetadata` on `turn/start` and
    `turn/steer`
    - protocol round-trip coverage for `Op::UserInputWithClientMetadata`
    - `TurnMetadataState` merge test proving client metadata is added
    without overwriting reserved built-in fields
    - websocket request-shape test proving outbound `response.create`
    contains merged metadata inside
    `client_metadata["x-codex-turn-metadata"]`
    - app-server integration tests proving:
    - `turn/start` forwards `clientMetadata` into the outbound Responses
    request path
      - websocket warmup + real turn request both behave correctly
      - `turn/steer` updates the follow-up request metadata
    
    ### Commands run
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core
    turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields
    --lib`
    - `cargo test -p codex-core --test all
    responses_websocket_preserves_custom_turn_metadata_fields`
    - `cargo test -p codex-app-server --test all client_metadata`
    - `cargo test -p codex-app-server --test all
    turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2
    -- --nocapture`
    - `just fmt`
    - `just fix -p codex-core -p codex-protocol -p codex-app-server-protocol
    -p codex-app-server`
    - `just fix -p codex-exec -p codex-tui-app-server`
    - `just argument-comment-lint`
    
    ### Full suite note
    `cargo test` in `codex-rs` still fails in:
    -
    `suite::v2::turn_interrupt::turn_interrupt_resolves_pending_command_approval_request`
    
    I verified that same failure on a clean detached `HEAD` worktree with an
    isolated `CARGO_TARGET_DIR`, so it is not caused by this patch.
  • [codex] Show ctrl + t hint on truncated exec output in TUI (#17076)
    ## What
    
    Show an inline `ctrl + t to view transcript` hint when exec output is
    truncated in the main TUI chat view.
    
    ## Why
    
    Today, truncated exec output shows `… +N lines`, but it does not tell
    users that the full content is already available through the existing
    transcript overlay. That makes hidden output feel lost instead of
    discoverable.
    
    This change closes that discoverability gap without introducing a new
    interaction model.
    
    Fixes: CLI-5740
    
    ## How
    
    - added an output-specific truncation hint in `ExecCell` rendering
    - applied that hint in both exec-output truncation paths:
      - logical head/tail truncation before wrapping
      - row-budget truncation after wrapping
    - preserved the existing row-budget behavior on narrow terminals by
    reserving space for the longer hint line
    - updated the relevant snapshot and added targeted regression coverage
    
    ## Intentional design decisions
    
    - **Aligned shortcut styling with the visible footer UI**  
    The inline hint uses `ctrl + t`, not `Ctrl+T`, to match the TUI’s
    rendered key-hint style.
    
    - **Kept the noun `transcript`**  
    The product already exposes this flow as the transcript overlay, so the
    hint points at the existing concept instead of inventing a new label.
    
    - **Preserved narrow-terminal behavior**  
    The longer hint text is accounted for in the row-budget truncation path
    so the visible output still respects the existing viewport cap.
    
    - **Did not add the hint to long command truncation**  
    This PR only changes hidden **output** truncation. Long command
    truncation still uses the plain ellipsis form because `ctrl + t` is not
    the same kind of “show hidden output” escape hatch there.
    
    - **Did not widen scope to other truncation surfaces**  
    This does not change MCP/tool-call truncation in `history_cell.rs`, and
    it does not change transcript-overlay behavior itself.
    
    ## Validation
    
    ### Automated
    - `just fmt`
    - `cargo test -p codex-tui`
    
    ### Manual
    - ran `just tui-with-exec-server`
    - executed `!seq 1 200`
    - confirmed the main view showed the new `ctrl + t to view transcript`
    truncation hint
    - pressed `ctrl + t` and confirmed the transcript overlay still exposed
    the full output
    - closed the overlay and returned to the main view
    
    ## Visual proof
    
    Screenshot/video attached in the PR UI showing:
    - the truncated exec output row with the new hint
    - the transcript overlay after `ctrl + t`
  • chore: merge name and title (#17116)
    Merge title and name concept to leverage the sqlite title column and
    have more efficient queries
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Render statusline context as a meter (#17170)
    Problem: The statusline reported context as an “X% left” value, which
    could be mistaken for quota, and context usage was included in the
    default footer.
    
    Solution: Render configured context status items as a filling context
    meter, preserve `context-used` as a legacy alias while hiding it from
    the setup menu, and remove context from the default statusline. It will
    still be available as an opt-in option for users who want to see it.
    
    <img width="317" height="39" alt="image"
    src="https://github.com/user-attachments/assets/3aeb39bb-f80d-471f-88fe-d55e25b31491"
    />
  • feat: advanced announcements per OS and plans (#17226)
    Support things like
    ```
      [[announcements]]
      content = "custom message"
      from_date = "2026-04-09"
      to_date = "2026-06-01"
      target_app = "cli"
      target_plan_types = ["pro"]
      target_oses = ["macos"]
      version_regex = "..." # add version of the patch
      ```
  • feat: /resume per ID/name (#17222)
    Support `/resume 00000-0000-0000-00000000` from the TUI (equivalent for
    the name)
  • Skip update prompts for source builds (#17186)
    Addresses #17166
    
    Problem: Source builds report version 0.0.0, so the TUI update path can
    treat any released Codex version as upgradeable and show startup or
    popup prompts.
    
    Solution: Skip both TUI update prompt entry points when the running CLI
    version is the source-build sentinel 0.0.0.
  • Add TUI notification condition config (#17175)
    Problem: TUI desktop notifications are hard-gated on terminal focus, so
    terminal/IDE hosts that want in-focus notifications cannot opt in.
    
    Solution: Add a flat `[tui] notification_condition` setting (`unfocused`
    by default, `always` opt-in), carry grouped TUI notification settings
    through runtime config, apply method + condition together in the TUI,
    and regenerate the config schema.
  • Add realtime voice selection (#17176)
    - Add realtime voice selection for realtime/start.
    - Expose the supported v1/v2 voice lists and cover explicit, configured,
    default, and invalid voice paths.
  • Move default realtime prompt into core (#17165)
    - Adds a core-owned realtime backend prompt template and preparation
    path.
    - Makes omitted realtime start prompts use the core default, while null
    or empty prompts intentionally send empty instructions.
    - Covers the core realtime path and app-server v2 path with integration
    coverage.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Fix stale thread-name resume lookups (#16646)
    Addresses #15943
    
    Problem: Name-based resume could stop on a newer session_index entry
    whose rollout was never persisted, shadowing an older saved thread with
    the same name.
    
    Solution: Materialize rollouts before indexing thread names and make
    name lookup skip unresolved entries until it finds a persisted rollout.
  • Support Warp for OSC 9 notifications (#17174)
    Problem: Warp supports OSC 9 notifications, but the TUI's automatic
    notification backend selection did not recognize its
    `TERM_PROGRAM=WarpTerminal` environment value.
    
    Solution: Treat `TERM_PROGRAM=WarpTerminal` as OSC 9-capable when
    choosing the TUI desktop notification backend.
  • 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>
  • [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>
  • 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.