Commit Graph

6131 Commits

  • Add stdio exec-server listener (#20663)
    ## Why
    
    This stack adds configured exec-server environments, including
    environments reached over stdio. Before client-side stdio transports or
    config can use that path, the exec-server binary itself needs a
    first-class stdio listen mode so it can speak the same JSON-RPC protocol
    over stdin/stdout that it already speaks over websockets.
    
    **Stack position:** this is PR 1 of 5. It is the server-side transport
    foundation for the stack.
    
    ## What Changed
    
    - Accept `stdio` and `stdio://` for `codex exec-server --listen`.
    - Promote the existing stdio `JsonRpcConnection` helper from test-only
    code into normal exec-server transport code.
    - Add parse coverage for stdio listen URLs while preserving the existing
    websocket default.
    
    ## Stack
    
    - **1. This PR:** https://github.com/openai/codex/pull/20663 - Add stdio
    exec-server listener
    - 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server
    client transport
    - 3. https://github.com/openai/codex/pull/20665 - Make environment
    providers own default selection
    - 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME
    environments TOML provider
    - 5. https://github.com/openai/codex/pull/20667 - Load configured
    environments from CODEX_HOME
    
    Split from original draft: https://github.com/openai/codex/pull/20508
    
    ## Validation
    
    Not run locally; this was split out of the original draft stack.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Fix Windows PTY teardown by preserving ConPTY ownership (#20685)
    ## Why
    
    On Windows, background terminals could stay visible after their shell
    process had already exited. The elevated runner waits for the PTY output
    reader to reach EOF before it sends the final exit message, but the
    ConPTY helper was reducing ownership down to raw handles too early. That
    left the pseudoconsole's borrowed pipe handles alive past teardown, so
    EOF never propagated and the session stayed `running`.
    
    ## What changed
    
    - change `utils/pty/src/win/conpty.rs` to hand off owned ConPTY
    resources instead of leaking only raw handles
    - make `windows-sandbox-rs/src/conpty/mod.rs` keep the pseudoconsole
    owner and the backing pipe handles together until teardown
    - update the elevated runner and the legacy unified-exec backend to keep
    that `ConptyInstance` alive, take only the specific pipe handles they
    need, and drop the owner at teardown instead of trying to close a
    detached pseudoconsole handle later
    
    ## Testing
    
    - desktop app in `Auto-review`: 11 x `cmd /c "ping -n 3 google.com"` all
    exited cleanly and did not accumulate in the UI
    - desktop app in `Auto-review`: 5 x `cmd /c "ping -n 30 google.com"`
    appeared in the UI and drained back out on their own
  • Prepare selected environment plumbing (#20669)
    ## Why
    This is a prep PR in the multi-environment process-tool stack. It
    separates ownership/config cleanup from the behavior change that teaches
    process tools to route by selected environment, so the follow-up PR can
    focus on model-facing `environment_id` behavior.
    
    ## Stack
    1. https://github.com/openai/codex/pull/20646 - `EnvironmentContext`
    rendering for selected environments
    2. https://github.com/openai/codex/pull/20669 - selected-environment
    ownership and tool config prep (this PR)
    3. https://github.com/openai/codex/pull/20647 - process-tool
    `environment_id` routing
    
    ## What Changed
    - keep the resolved turn environment list wrapped in
    `ResolvedTurnEnvironments` through `TurnContext` instead of unwrapping
    it back to a raw `Vec`
    - add `TurnContext::resolve_path_against` so cwd-relative path
    resolution has one shared helper
    - replace the old tool config boolean with `ToolEnvironmentMode::{None,
    Single, Multiple}`
    
    ## Testing
    - Tests not run locally; this prep refactor is covered by GitHub CI for
    the stack.
    
    Co-authored-by: Codex <noreply@openai.com>
  • tui: retire /approvals and rename /autoreview to /approve (#21034)
    ## Why
    
    The TUI currently exposes overlapping command names for the same
    permissions flow: `/permissions` and the older `/approvals` alias. It
    also uses `/autoreview` for the manual retry flow, even though the
    action users take there is approving one denied auto-review request.
    
    This change makes the command surface consistent with the hard rebrand:
    - `/permissions` is the only command for permission settings.
    - `/approve` is the command for approving a recent auto-review denial.
    
    ## What changed
    
    - Removed the legacy `/approvals` slash command and its dispatch path.
    - Kept `/permissions` as the single permissions command shown and
    accepted by the TUI.
    - Renamed the auto-review denial command from `/autoreview` to
    `/approve`.
    - Updated nearby comments so they refer to `/permissions` rather than
    the retired `/approvals` name.
    
    ## Verification
    
    - Updated the slash-command unit test to assert that `AutoReview` now
    renders and parses as `approve`.
  • feat(tui): add keymap debug inspector (#20794)
    ## Why
    
    We constantly get bug reports about keys not being recognized by Codex
    when the terminal is not handling the key press. Running `/keymap debug`
    or `/keymap` and going to the Debug tab, we can allow the user to either
    understand that the key being pressed is not being recognized or to
    check what it's being recognized as and report or reassign that key.
    
    | Menu | Inspector | Hint |
    |---|---|---|
    | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57 12"
    src="https://github.com/user-attachments/assets/512b6faa-344e-4aee-9c00-b4bdc633a662"
    /> | <img width="1261" height="754" alt="CleanShot 2026-05-02 at 12 56
    36"
    src="https://github.com/user-attachments/assets/a6ddae7d-e174-4ee4-893f-e6bec4fff4ab"
    /> | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57
    30"
    src="https://github.com/user-attachments/assets/db507784-f40a-4cff-ac23-a61d9703769b"
    /> |
    ## Summary
    - add a Debug tab to `/keymap` and support `/keymap debug` for direct
    access
    - show what key Codex receives, the config key representation, raw event
    details, and matching actions
    - add a progressive missing-key hint that escalates after a few seconds
    with no detected keypress
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-tui keymap_setup::tests::debug_view`
    - `cargo test -p codex-tui keymap_setup::tests`
    - `cargo test -p codex-tui slash_keymap`
    - `cargo test -p codex-tui` (unit tests passed; integration test
    `suite::model_availability_nux::resume_startup_does_not_consume_model_availability_nux_count`
    failed locally by itself with `codex resume` exiting 1 and terminal
    probe escape output)
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo insta pending-snapshots`
    - `git diff --check`
  • fix(linux-sandbox): fall back when system bwrap lacks perms (#20628)
    ## Why
    
    Codex `0.128` started using `--perms` in more routine Linux sandbox
    construction when protected workspace metadata mounts landed in #19852.
    Upstream bubblewrap added `--perms` in `v0.5.0`, so system `bwrap`
    versions older than that, including the `v0.4.0` and `v0.4.1` family, do
    not support the flag. The launcher still selected those binaries as long
    as they existed on `PATH`.
    
    That means affected hosts can fail every sandboxed command up front
    with:
    
    ```text
    bwrap: Unknown option --perms
    ```
    
    The reports in #20590 and duplicate #20623 match that compatibility gap;
    #20623 explicitly shows system bubblewrap `0.4.0`.
    
    ## What changed
    
    - Replace the single `--argv0` probe with a small system-bwrap
    capability probe in `codex-rs/linux-sandbox/src/launcher.rs`.
    - Continue using the old-system `--argv0` compatibility path when
    needed, but only select a system `bwrap` if it also advertises
    `--perms`.
    - Fall back to the vendored `bwrap` when the system binary is too old
    for the flags Codex now requires.
    - Add regression coverage for the old-system-bwrap case so binaries
    without `--perms` stay on the vendored path.
    
    ## Verification
    
    - Added `falls_back_to_vendored_when_system_bwrap_lacks_perms` to cover
    the reported compatibility gap.
    - Ran `cargo test -p codex-linux-sandbox` and `cargo clippy -p
    codex-linux-sandbox --tests` locally. On macOS, the crate builds but its
    Linux-only tests are cfg-gated out, so the new regression test still
    needs Linux CI or a Linux devbox run for real execution coverage.
    
    ## Related issues
    
    - Fixes #20590
    - Duplicate report: #20623
  • feat(app-server): always return limited thread history (#20682)
    ## Why
    
    Whenever we return a thread's history (turns and items) over app-server,
    always return the limited form as specified by the rollout policy
    `EventPersistenceMode::Limited`, even if the thread was previously
    started with `EventPersistenceMode::Extended`.
    
    We're finding it is quite unscalable to be returning the extended
    history, so let's apply the same filtering logic of the rollout policy
    when we load and return the thread's history.
    
    ## What Changed
    
    - Reuse the rollout persistence policy when reconstructing app-server
    `ThreadItem` history so only `EventPersistenceMode::Limited` rollout
    items are replayed into API turns.
    - Route `thread/read`, `thread/resume`, `thread/fork`,
    `thread/turns/list`, and rollback responses through the same filtered
    app-server history projection.
    - Keep live active turns intact when composing a response for a
    currently running thread.
    - Update command execution coverage so persisted extended command events
    are excluded from returned history for `thread/read`, `thread/fork`, and
    `thread/turns/list`.
    
    ## Test Plan
    
    - `cargo test -p codex-app-server limited`
    - `cargo test -p codex-app-server thread_shell_command`
    - `cargo test -p codex-app-server thread_read`
    - `cargo test -p codex-app-server thread_rollback`
    - `cargo test -p codex-app-server thread_fork`
    - `cargo test -p codex-app-server-protocol`
  • Unify skip-review handling for approval_mode = "approve" (#20750)
    ## Summary
    - Treat `approval_mode = "approve"` as skip-review across all permission
    modes.
    - Remove the mode-specific split in the MCP auto-approval gate so
    approved tools bypass review consistently.
    - Expand regression coverage in the shared MCP helper and the core
    tool-call flow.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core
    approve_mode_skips_arc_and_guardian_in_every_permission_mode`
    - `git diff --check`
    - Full `cargo test -p codex-core` was also attempted, but the suite hit
    an unrelated pre-existing stack overflow in an existing multi-agent test
  • [mcp-apps] Persist MCP Apps specific tool call end event. (#20853)
    - [x] Persist a special type of MCP tool calls for triggering MCP App,
    this type of mcp tool calls has 'mcpAppResourceUri` set. These events
    are needed so that the Codex App can correctly render the MCP App after
    resume.
  • core: share responses request builder with compact requests (#20989)
    ## Why
    
    `ModelClientSession` and `compact_conversation_history()` were still
    rebuilding the same `ResponsesApiRequest` fields separately. That
    duplication makes it easy for normal `/responses` turns and compact
    requests to drift when request-shape changes land later, which is
    exactly the kind of cache-affecting divergence we want to avoid.
    
    This follow-up keeps the scope small by extracting the shared
    request-construction logic into one helper and using it from both paths.
    
    ## What changed
    
    - move `ResponsesApiRequest` construction into a shared
    `ModelClient::build_responses_request(...)` helper in
    `core/src/client.rs`
    - update the normal `/responses` streaming path to call that helper
    instead of the old `ModelClientSession`-local implementation
    - update `compact_conversation_history()` to derive its compact payload
    from the same helper so `model`, `instructions`, `input`, `tools`,
    `parallel_tool_calls`, `reasoning`, and `text` stay aligned with normal
    request building
    - add a unit test covering the shared helper's prompt cache key,
    installation metadata, and `service_tier` behavior
    
    ## Verification
    
    - `cargo test -p codex-core
    build_responses_request_sets_shared_cache_and_metadata_fields`
    - `cargo test -p codex-core --test all
    remote_compact_v2_reuses_context_compaction_for_followups`
    
    ## Docs
    
    No docs update needed.
  • memories-mcp: reject symlink traversal in local backend (#21010)
    ## Why
    
    The local memories MCP backend only rejected symlinks after resolving
    the final path. That left room for scoped requests like
    `skills/secret.md` to walk through a symlinked ancestor directory and
    escape the configured memories root.
    
    This change also makes missing scoped paths fail explicitly instead of
    looking like an empty `list` / `search` result or a `NotFile` read
    error.
    
    ## What Changed
    
    - walk each scoped path component in
    `LocalMemoriesBackend::resolve_scoped_path` and reject symlinked
    ancestors before accessing the target
    - reject scoped paths that traverse through a non-directory intermediate
    component
    - add a `NotFound` backend error for missing `read`, `list`, and
    `search` paths and map it through the MCP server error conversion
    - add coverage for missing paths and symlinked ancestor directories in
    `codex-rs/memories/mcp/src/local_tests.rs`
    
    ## Testing
    
    - added unit coverage in `codex-rs/memories/mcp/src/local_tests.rs` for
    missing paths and symlinked ancestor directories across `read`, `list`,
    and `search`
  • memories/mcp: generate tool schemas with schemars (#21012)
    ## Why
    
    The memories MCP server currently keeps handwritten JSON Schema beside
    the Rust types that actually serialize and deserialize the tool
    payloads:
    [`schema.rs`](https://github.com/openai/codex/blob/2f5c06a29cdd68f11d07126dc56871bff1218ba1/codex-rs/memories/mcp/src/schema.rs#L4-L133),
    [`server.rs`](https://github.com/openai/codex/blob/2f5c06a29cdd68f11d07126dc56871bff1218ba1/codex-rs/memories/mcp/src/server.rs#L44-L75),
    and
    [`backend.rs`](https://github.com/openai/codex/blob/2f5c06a29cdd68f11d07126dc56871bff1218ba1/codex-rs/memories/mcp/src/backend.rs#L41-L117).
    That duplicates the tool contract and makes schema drift easier as the
    API evolves.
    
    ## What changed
    
    - derive `JsonSchema` for the memories tool arguments, responses, and
    nested response types
    - replace the handwritten schema builders with shared `schemars`
    generation
    - preserve the existing wire shape while generating schemas, including
    nullable output `Option` fields and non-nullable optional input fields
    - wire the `list`, `read`, and `search` tools to the generated schemas
    
    ## Verification
    
    - CI pending
  • [codex] Split app-server request processors (#20940)
    ## Why
    
    The app-server request path had grown around a large
    `CodexMessageProcessor` plus separate API wrapper/helper modules. That
    made the dependency graph hard to see and forced unrelated request
    families to share broad processor state.
    
    This PR makes the split mechanical and command-prefix oriented so
    request families own only the dependencies they use.
    
    ## What changed
    
    - Replaced `CodexMessageProcessor` with command-prefix request
    processors under `app-server/src/request_processors/`.
    - Removed the old config, device-key, external-agent-config, and fs API
    wrapper files by moving their API handling into processors.
    - Split apps, plugins, marketplace, catalog, account, MCP, command exec,
    fs, git, feedback, thread, turn, thread goals, and Windows sandbox
    handling into dedicated processors.
    - Kept shared lifecycle, summary conversion, token usage replay, and
    shared error mapping only where multiple processors use them; single-use
    helpers were inlined into their owning processor.
    - Removed the fallback processor path and moved processor tests to
    `_tests` files.
    
    ## Validation
    
    - `cargo test -p codex-app-server`
    - `cargo check -p codex-app-server`
    - `just fix -p codex-app-server`
  • Keep paused goals paused on thread resume (#20790)
    ## Summary
    
    Early adopters of the `/goal` feature have provided feedback that they
    expect a goal they explicitly paused to remain paused when they resume a
    thread. Previously, resuming a thread would reactivate a paused goal.
    
    This PR keeps persisted goal status unchanged during thread resume. This
    honors the user feedback while also simplifying the core goal logic.
    
    Rather than have the core logic automatically resume a paused goal, that
    responsibility is transferred to the client. The TUI now detects a
    resumed thread with a paused goal and asks the user whether to `Resume
    goal` or `Leave paused`. The prompt appears only for quiet resume flows,
    so users who resume with an immediate prompt are not interrupted.
    
    <img width="544" height="111" alt="image"
    src="https://github.com/user-attachments/assets/0ac9de1c-6ee6-47ba-b223-c03c8eb4c192"
    />
  • Speed up /side parent restore replay (#20815)
    ## Why
    
    Returning from a `/side` conversation restores the parent thread by
    replaying its snapshot into the TUI. For very long parent threads,
    replaying every transcript row can take noticeable time even though most
    rows immediately scroll out of terminal history.
    
    ## What Changed
    
    - Buffer thread-switch replay for parent restores when terminal resize
    reflow is enabled.
    - Reuse the existing resize-reflow tail renderer so only the retained
    transcript tail is written back to scrollback when a row cap is
    configured.
  • Keep paused goals paused on thread resume (#20790)
    ## Summary
    
    Early adopters of the `/goal` feature have provided feedback that they
    expect a goal they explicitly paused to remain paused when they resume a
    thread. Previously, resuming a thread would reactivate a paused goal.
    
    This PR keeps persisted goal status unchanged during thread resume. This
    honors the user feedback while also simplifying the core goal logic.
    
    Rather than have the core logic automatically resume a paused goal, that
    responsibility is transferred to the client. The TUI now detects a
    resumed thread with a paused goal and asks the user whether to `Resume
    goal` or `Leave paused`. The prompt appears only for quiet resume flows,
    so users who resume with an immediate prompt are not interrupted.
    
    <img width="544" height="111" alt="image"
    src="https://github.com/user-attachments/assets/0ac9de1c-6ee6-47ba-b223-c03c8eb4c192"
    />
  • feat: support multi-query memories search (#21004)
    ## Why
    The memories MCP `search` tool only accepts a single substring today,
    which makes it hard for clients to express combined queries or explain
    why a line matched. This change adds the richer search shape needed for
    the next client iteration while keeping the legacy single-`query` call
    working.
    
    ## What changed
    - accept either the legacy `query` field or a new `queries` array, plus
    `match_mode: any|all`
    - teach the local memories backend to evaluate multi-query line matches
    and return `matched_queries` on each hit
    - update the MCP input/output schema and add coverage for parser
    behavior, ordering, pagination, case sensitivity, and match modes
    
    ## Testing
    - added unit coverage in `memories/mcp/src/local_tests.rs` and
    `memories/mcp/src/server.rs`
  • feat: add context lines to memories MCP search (#20997)
    ## Why
    
    The paginated memories MCP `search` tool still returned only the
    matching line text, which made it harder for clients to present useful
    search results or decide whether they needed to follow up with a
    separate `read` call. Adding a small amount of surrounding context makes
    individual hits much more usable while keeping the search response
    deterministic and line-addressable.
    
    ## What changed
    
    - add an optional `context_lines` search argument and thread it through
    the MCP server into the local memories backend
    - change search matches to return the matched `line_number` plus a
    `start_line_number` and multi-line `content` block for the requested
    context window
    - update the search tool schema and description to document the new
    request/response shape
    - extend the local backend tests to cover zero-context matches,
    contextual results, pagination, and invalid cursors that point past the
    end of the result set
    
    ## Testing
    
    - Added targeted unit coverage in `memories/mcp/src/local_tests.rs`
    - GitHub Actions are running for the branch
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: paginate memories MCP search results (#20996)
    ## Why
    
    The memories MCP `search` tool previously stopped once it hit
    `max_results`, so callers could tell there were more matches via
    `truncated` but had no way to fetch the rest of the result set. That
    made large searches awkward for clients that need to keep paging through
    a stable, deterministic view of the matches.
    
    ## What changed
    
    - add an optional `cursor` field to `SearchMemoriesRequest` / tool input
    and return `next_cursor` in `SearchMemoriesResponse`
    - update the MCP schemas and tool wiring so clients can request
    subsequent pages explicitly
    - change the local memories backend to collect and sort the full scoped
    match list, then slice the requested page and reject invalid cursors
    - add unit coverage for paginated search results and invalid cursor
    handling in `memories/mcp/src/local_tests.rs`
    
    ## Testing
    
    - Added targeted unit coverage in `memories/mcp/src/local_tests.rs`
    - GitHub Actions are running for the branch
  • feat: make memories MCP list shallow (#20994)
    ## Why
    The memories MCP `list` tool should behave like a directory listing, not
    a recursive tree walk. Recursive results make pagination harder to
    reason about, return unexpectedly deep paths for scoped requests, and no
    longer match the intended tool contract.
    
    ## What Changed
    - Changed the local memories backend so `list` returns only the
    immediate children of the requested path.
    - Preserved file-scoped requests by returning the file itself, and
    missing paths by returning an empty result.
    - Updated cursor handling to paginate over the shallow sibling set and
    reject cursors past the available results.
    - Updated the MCP tool description to say it lists immediate files and
    directories under a path.
    - Reworked the local backend tests to cover shallow top-level listing,
    shallow scoped listing, sibling ordering, and pagination.
    
    ## Testing
    - `cargo test -p codex-memories-mcp`
  • feat: paginate MCP memories list (#20993)
    ## Why
    
    Large memories trees do not fit well into a single MCP `list` response.
    This change makes the memories MCP server page `list` results so callers
    can continue walking the tree without overfetching or relying on
    ambiguous truncation.
    
    ## What changed
    
    - add an optional `cursor` input to the memories MCP `list` API and
    return `next_cursor` alongside `truncated` in the response
    - paginate recursive local-memory traversal while preserving
    lexicographic path order across directories
    - reject malformed and out-of-range cursors as invalid MCP requests
    - update the server/schema wiring and add coverage for pagination,
    ordering, and cursor validation in `memories/mcp/src/local_tests.rs`
    
    ## Testing
    
    - `cargo test -p codex-memories-mcp`
  • feat: add max_lines to memories MCP read (#20991)
    ## Why
    
    The memories MCP `read` tool already supports `line_offset`, but it
    cannot return a bounded line range. That makes it awkward to page
    through large memory files or request a small slice without relying on
    token truncation.
    
    ## What changed
    
    - add an optional `max_lines` parameter to the memories MCP `read` tool
    schema and request parsing
    - cap local backend reads to the requested number of lines before token
    truncation
    - treat `max_lines = 0` as an invalid request and surface it as
    `invalid_params`
    - add backend tests for bounded reads and invalid line request
    validation
    
    ## Testing
    
    - added coverage in `memories/mcp/src/local_tests.rs` for `max_lines`
    reads and invalid `max_lines` / `line_offset` requests
  • feat: add line offsets to memory read MCP (#20986)
    ## Why
    
    Memory clients sometimes need to continue reading a file from a known
    line instead of starting over from the top. Adding a line offset to the
    `read` MCP keeps that resume logic simple and avoids re-reading
    already-consumed content.
    
    ## What changed
    
    - Added an optional `line_offset` argument to the memory `read` tool,
    defaulting to `1`.
    - Read content starting at the requested 1-indexed line before token
    truncation, and return `start_line_number` in the response.
    - Treat invalid offsets as invalid params errors and cover the new
    behavior in `codex-rs/memories/mcp/src/local_tests.rs`.
    
    ## Testing
    
    - Added unit tests for reading from a non-default starting line.
    - Added unit tests for rejecting `0` and past-end line offsets.
  • feat: add remote compaction v2 Responses client path (#20773)
    ## Why
    
    This adds the `remote_compaction_v2` client path so remote compaction
    can run through the normal Responses stream and install a
    `context_compaction` item that trigger a compaction.
    
    The goal is to migrate some of the compaction logic on the client side
    
    We keeps the v2 transport behind a feature flag while letting follow-up
    requests reuse the compacted context instead of falling back to the
    legacy compaction item shape.
    
    ## What changed
    
    - add `ResponseItem::ContextCompaction` and refresh the generated
    app-server / schema / TypeScript fixtures that expose response items on
    the wire
    - add `core/src/compact_remote_v2.rs` to send compaction through the
    standard streamed Responses client, require exactly one
    `context_compaction` output item, and install that item into compacted
    history
    - route manual compact and auto-compaction through the v2 path when
    `remote_compaction_v2` is enabled, while keeping the existing remote
    compaction path as the fallback
    - preserve the new item type across history retention, follow-up request
    construction, telemetry, rollout persistence, and rollout-trace
    normalization
    - add targeted coverage for the feature flag, `context_compaction`
    serialization, rollout-trace normalization, and remote-compaction
    follow-up behavior
    
    ## Verification
    
    - added protocol tests for `context_compaction`
    serialization/deserialization in `protocol/src/models.rs`
    - added rollout-trace coverage for `context_compaction` normalization in
    `rollout-trace/src/reducer/conversation_tests.rs`
    - added remote compaction integration coverage for v2 follow-up reuse
    and mixed compaction output streams in
    `core/tests/suite/compact_remote.rs`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: memories mcp v1 (#20622)
    Add an experimental MCP on memories
    This must never be used and is only here for testing purpose
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: support template interpolation in multi-agent usage hints (#20973)
    ## Why
    
    `multi_agent_v2` usage hints sometimes need to reference resolved config
    values such as the effective thread limit. Those values only exist after
    config layering, defaulting, and feature materialization, so the raw
    TOML alone was not enough to render them.
    
    ## What changed
    
    - allow
    `features.multi_agent_v2.{usage_hint_text,root_agent_usage_hint_text,subagent_usage_hint_text}`
    to use `{{ ... }}` placeholders backed by the materialized effective
    config
    - fail config loading with a targeted error when a referenced
    placeholder does not exist or does not resolve to a scalar value
    - move resolved-config materialization into a shared helper so config
    interpolation and config-lock export/replay both serialize the same
    resolved feature, memory, and agent settings
    
    ## Example
    ```
    [features.multi_agent_v2]
    enabled = true
    usage_hint_text = "lorem {{ features.multi_agent_v2.max_concurrent_threads_per_session }} ipsum"
    ```
    gets rendered as 
    ```
            "description": String("... \lorem 4 ipsum"),
    ```
  • [codex] Emit MCP tool calls as turn items (#20677)
    ## Why
    
    `McpToolCall` was still an app-server item synthesized from deprecated
    legacy begin/end events. Recent item migrations moved this ownership
    into core `TurnItem`s, so MCP tool calls now follow the same canonical
    lifecycle and leave legacy events as compatibility fanout.
    
    Keeping the core item close to the v2 `ThreadItem::McpToolCall` shape
    also avoids spreading MCP result semantics across app-server conversion
    code. Core now owns whether a completed call is `completed` or `failed`,
    and whether the payload is a tool result or an error.
    
    ## What changed
    
    - Added core `TurnItem::McpToolCall` with flattened `server`, `tool`,
    `arguments`, `status`, `result`, and `error` fields.
    - Updated MCP tool call emitters, including MCP resource tools, to emit
    `ItemStarted`/`ItemCompleted` around directly constructed core MCP
    items.
    - Updated app-server v2 conversion to project the core MCP item into
    `ThreadItem::McpToolCall` without deriving status or splitting `Result`
    locally.
    - Ignored live deprecated MCP legacy fanout in app-server v2 to avoid
    duplicate item notifications, while keeping thread history replay on the
    legacy event path.
    
    ## Verification
    
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core --lib mcp_tool_call`
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server
    mcp_tool_call_completion_notification_contains_truncated_large_result`
  • [codex] Refactor app-server dispatch result flow (#20897)
    ## Why
    
    App-server request handling had response sending spread across many
    individual handlers, which made it harder to see which requests return
    payloads, which methods send their own delayed response, and which
    branches emit notifications after a response.
    
    ## What changed
    
    - Centralized normal `ClientResponsePayload` sending in the dispatch
    path.
    - Kept explicit-response methods explicit where they need custom
    ordering or delayed delivery.
    - Removed forward-only handler wrappers and immediate `async { ...
    }.await` bodies where they were not needed.
    - Moved branch-specific post-response notifications into the branches
    that own the response ordering.
    - Replaced unreachable delegated request-family error arms with explicit
    `unreachable!` cases.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server thread_goal`
    - `just fix -p codex-app-server`
  • Remove local docs and specs (#20896)
    ## Summary
    
    We should not check local-only docs or planning specs into this
    repository. Keeping those files here duplicates the canonical Codex
    documentation surface and makes transient implementation notes look like
    supported docs.
    
    This PR removes the local-only docs/spec files from `docs/` and trims
    `docs/config.md` back to links for the maintained configuration
    documentation on developers.openai.com.
  • [codex] Add issue labeler area labels (#20893)
    ## Why
    
    The automated issue labeler needs more precise area labels for newly
    opened GitHub issues so triage can distinguish new Codex app and agent
    feature surfaces without falling back to broad labels.
    
    ## What Changed
    
    - Added labeler prompt entries for `computer-use`, `browser`, `memory`,
    `imagen`, `remote`, `performance`, `automations`, and `pets` in
    `.github/workflows/issue-labeler.yml`.
    - Updated the agent-area guidance so `memory` is used for agentic memory
    storage/retrieval and `performance` is used for slow behavior, high
    memory utilization, and leaks.
    - Expanded the fallback `agent` guidance so Codex prefers the new
    specific labels when applicable.
    
    ## Verification
    
    - Parsed `.github/workflows/issue-labeler.yml` with `yq e '.'`.
    - Ran `git diff --check` for the workflow change.
  • Bound websocket request sends with idle timeout (#20751)
    ## Why
    
    We saw Responses websocket sessions recover only after a long quiet
    period when the server had already logged the websocket as disconnected.
    The normal connect path is already bounded by
    `websocket_connect_timeout_ms`, but the first request send on an
    established websocket reused only the receive-side idle timeout after
    the write completed. If the socket write/pump stalls, the client can sit
    in `ws_stream.send(...)` without reaching the existing receive timeout.
  • [tool_suggest] More prompt polishes. (#20566)
    Tool suggest still misfires when model needs tool_search, updating the
    prompts to further disambiguate it:
    
    - [x] rename it from `tool_suggest` to `request_plugin_install`
    - [x] rephrase "suggestion" to "install" in the tool descriptions.
    - [x] disambiguate "the tool" vs "the plugin/connector". 
    
    Tested with the Codex App and verified it still works.
  • fix(tui): bound startup terminal probes (#20654)
    ## Summary
    
    Bound TUI startup terminal response probes so unsupported terminals
    cannot stall startup for multiple seconds.
    
    This replaces the Unix startup uses of crossterm's blocking response
    probes with short `/dev/tty` probes that use nonblocking reads and
    `poll` with a 100ms timeout. It covers the initial cursor-position
    query, keyboard enhancement support detection, and OSC 10/11
    default-color detection. The default-color probe uses one shared
    deadline for foreground and background instead of allowing two
    independent full waits.
    
    The diagnostic mode/trace env vars from the investigation branch are
    intentionally not included. The shipped behavior is simply bounded
    probing by default, while non-Unix keeps the existing crossterm fallback
    path.
    
    ## Details
    
    - Add a private `terminal_probe` module for bounded Unix terminal probes
    and response parsers.
    - Let `custom_terminal::Terminal` accept a caller-provided initial
    cursor position so startup can compute it before constructing the
    terminal.
    - Use bounded cursor, keyboard enhancement, and default-color probes on
    Unix startup.
    - Preserve default-color cache behavior so a failed attempted query does
    not retry forever.
    
    ## Validation
    
    - `cd codex-rs && just fmt`
    - `cd codex-rs && cargo test -p codex-tui terminal_probe`
    - `cd codex-rs && just fix -p codex-tui`
    - `cd codex-rs && just argument-comment-lint`
    - `git diff --check`
    - `git diff --cached --check`
    
    `cd codex-rs && cargo test -p codex-tui` still aborts on the
    pre-existing local stack overflow in
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`;
    I reproduced that same focused failure on `main` before this PR work, so
    it is not introduced by this change.
    
    Manual validation in the VM showed the original crossterm path taking
    about 2s per unanswered probe, while bounded probing returned in about
    100ms per probe.
  • Fix custom CA login behind TLS-inspecting proxies (#20676)
    Refs:
    https://linear.app/openai/issue/SE-6311/login-fails-for-experian-users-behind-tls-inspecting-proxy
    
    ## Summary
    - When a custom CA bundle is configured, force the shared `codex-client`
    reqwest builder onto rustls before registering custom roots.
    - Add the `rustls-tls-native-roots` reqwest feature so the rustls client
    preserves native roots plus the enterprise CA bundle.
    - Add subprocess TLS coverage for both a direct local TLS 1.3 server and
    a hermetic local CONNECT TLS-intercepting proxy that forwards a
    token-exchange-shaped POST to a local origin.
    
    ## Plain-language explanation
    Experian users are behind a TLS-inspecting proxy, so the login token
    exchange needs to trust the enterprise CA bundle from
    `CODEX_CA_CERTIFICATE` or `SSL_CERT_FILE`. Before this change, that
    custom-CA branch still used reqwest default TLS selection, which could
    fail in the proxy environment. Now, only when a custom CA is configured,
    Codex selects rustls first and then adds the custom CA roots, matching
    the validated behavior from the Experian test build while leaving normal
    system-root clients unchanged.
    
    The new regression test recreates the enterprise-proxy shape locally:
    the probe client sends an HTTPS `POST /oauth/token` through an explicit
    HTTP CONNECT proxy, the proxy presents a leaf certificate signed by a
    runtime-generated test CA, decrypts the request, forwards it to a local
    origin, and relays the `ok` response back.
    
    ## Scope note
    - The actual production fix is the first commit: `8368119282 Fix custom
    CA reqwest clients to use rustls`.
    - The second commit is integration-test coverage only. It generates all
    test CA and localhost certificate material at runtime.
    
    ## Validation
    - `cd codex-rs && cargo test -p codex-client --test ca_env
    posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle
    -- --nocapture`
    - `cd codex-rs && cargo test -p codex-client`
    - `cd codex-rs && cargo test -p codex-login`
    - `cd codex-rs && just fmt`
    - `cd codex-rs && just bazel-lock-update`
    - `cd codex-rs && just bazel-lock-check`
    - `cd codex-rs && just fix -p codex-client`
  • ci: cross-compile Windows Bazel clippy (#20701)
    ## Why
    
    #20585 moved the Windows Bazel test job to the cross-compile path, but
    the Windows Bazel clippy and verify-release-build jobs were still using
    the native Windows/MSVC-host fallback. Those two jobs became the slowest
    Windows PR legs, even though both are build-only signal and do not need
    to execute the resulting binaries.
    
    ## What Changed
    
    - Switches the Windows Bazel clippy job from
    `--windows-msvc-host-platform` to `--windows-cross-compile`, so clippy
    build actions use Linux RBE while still targeting
    `x86_64-pc-windows-gnullvm`.
    - Switches the Windows Bazel verify-release-build job to
    `--windows-cross-compile` as well. This job only compiles
    `cfg(not(debug_assertions))` Rust code under `fastbuild`, so it does not
    need a native Windows build host.
    - Keeps the old `--skip_incompatible_explicit_targets` behavior only for
    fork/community PRs without `BUILDBUDDY_API_KEY`, where `run-bazel-ci.sh`
    falls back to the local Windows MSVC-host shape.
    - Adds `--windows-cross-compile` support to
    `.github/scripts/run-bazel-query-ci.sh`, so target-discovery queries
    select the same `ci-windows-cross` config as the subsequent build.
    - Threads that option through `scripts/list-bazel-clippy-targets.sh` so
    the Windows clippy job discovers targets under the same platform shape
    as the subsequent clippy build.
    
    ## Verification
    
    Local checks:
    
    ```shell
    bash -n .github/scripts/run-bazel-query-ci.sh
    bash -n scripts/list-bazel-clippy-targets.sh
    ruby -e 'require "yaml"; YAML.load_file(".github/workflows/bazel.yml"); puts "ok"'
    RUNNER_OS=Linux ./scripts/list-bazel-clippy-targets.sh | grep -c -- '-windows-cross-bin$'
    RUNNER_OS=Windows ./scripts/list-bazel-clippy-targets.sh --windows-cross-compile | grep -c -- '-windows-cross-bin$'
    ```
    
    The Linux target-list check reported `0` Windows-cross internal test
    binaries, while the Windows cross target-list check reported `47`,
    preserving the test-code clippy coverage shape from the existing Windows
    job.
  • ci: cross-compile Windows Bazel tests (#20585)
    ## Status
    
    This is the Bazel PR-CI cross-compilation follow-up to #20485. It is
    intentionally split from the Cargo/cargo-xwin release-build PoC so
    #20485 can stay as the historical release-build exploration. The
    unrelated async-utils test cleanup has been moved to #20686, so this PR
    is focused on the Windows Bazel CI path.
    
    The intended tradeoff is now explicit in `.github/workflows/bazel.yml`:
    pull requests get the fast Windows cross-compiled Bazel test leg, while
    post-merge pushes to `main` run both that fast cross leg and a fully
    native Windows Bazel test leg. The native main-only job keeps full
    V8/code-mode coverage and gets a 40-minute timeout because it is less
    latency-sensitive than PR CI. All other Bazel jobs remain at 30 minutes.
    
    ## Why
    
    Windows Bazel PR CI currently does the expensive part of the build on
    Windows. A native Windows Bazel test job on `main` completed in about
    28m12s, leaving very little headroom under the 30-minute job timeout and
    making Windows the slowest PR signal.
    
    #20485 showed that Windows cross-compilation can be materially faster
    for Cargo release builds, but PR CI needs Bazel because Bazel owns our
    test sharding, flaky-test retries, and integration-test layout. This PR
    applies the same high-level shape we already use for macOS Bazel CI:
    compile with remote Linux execution, then run platform-specific tests on
    the platform runner.
    
    The compromise is deliberately signal-aware: code-mode/V8 changes are
    rare enough that PR CI can accept losing the direct V8/code-mode
    smoke-test signal temporarily, while `main` still runs the native
    Windows job post-merge to catch that class of regression. A follow-up PR
    should investigate making the cross-built Windows gnullvm V8 archive
    pass the direct V8/code-mode tests so this tradeoff can eventually go
    away.
    
    ## What Changed
    
    - Adds a `ci-windows-cross` Bazel config that targets
    `x86_64-pc-windows-gnullvm`, uses Linux RBE for build actions, and keeps
    `TestRunner` actions local on the Windows runner.
    - Adds explicit Windows platform definitions for
    `windows_x86_64_gnullvm`, `windows_x86_64_msvc`, and a bridge toolchain
    that lets gnullvm test targets execute under the Windows MSVC host
    platform.
    - Updates the Windows Bazel PR test leg to opt into the cross-compile
    path via `--windows-cross-compile` and `--remote-download-toplevel`.
    - Adds a `test-windows-native-main` job that runs only for `push` events
    on `refs/heads/main`, uses the native Windows Bazel path, includes
    V8/code-mode smoke tests, and has `timeout-minutes: 40`.
    - Keeps fork/community PRs without `BUILDBUDDY_API_KEY` on the previous
    local Windows MSVC-host fallback, including
    `--host_platform=//:local_windows_msvc` and `--jobs=8`.
    - Preserves the existing integration-test shape on non-gnullvm
    platforms, while generating Windows-cross wrapper targets only for
    `windows_gnullvm`.
    - Resolves `CARGO_BIN_EXE_*` values from runfiles at test runtime,
    avoiding hard-coded Cargo paths and duplicate test runfiles.
    - Extends the V8 Bazel patches enough for the
    `x86_64-pc-windows-gnullvm` target and Linux remote execution path.
    - Makes the Windows sandbox test cwd derive from `INSTA_WORKSPACE_ROOT`
    at runtime when Bazel provides it, because cross-compiled binaries may
    contain Linux compile-time paths.
    - Keeps the direct V8/code-mode unit smoke tests out of the Windows
    cross PR path for now while native Windows CI continues to cover them
    post-merge.
    
    ## Command Shape
    
    The fast Windows PR test leg invokes the normal Bazel CI wrapper like
    this:
    
    ```shell
    ./.github/scripts/run-bazel-ci.sh \
      --print-failed-action-summary \
      --print-failed-test-logs \
      --windows-cross-compile \
      --remote-download-toplevel \
      -- \
      test \
      --test_tag_filters=-argument-comment-lint \
      --test_verbose_timeout_warnings \
      --build_metadata=COMMIT_SHA=${GITHUB_SHA} \
      -- \
      //... \
      -//third_party/v8:all \
      -//codex-rs/code-mode:code-mode-unit-tests \
      -//codex-rs/v8-poc:v8-poc-unit-tests
    ```
    
    With the BuildBuddy secret available on Windows, the wrapper selects
    `--config=ci-windows-cross` and appends the important Windows-cross
    overrides after rc expansion:
    
    ```shell
    --host_platform=//:rbe
    --shell_executable=/bin/bash
    --action_env=PATH=/usr/bin:/bin
    --host_action_env=PATH=/usr/bin:/bin
    --test_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}
    ```
    
    The native post-merge Windows job intentionally omits
    `--windows-cross-compile` and does not exclude the V8/code-mode unit
    targets:
    
    ```shell
    ./.github/scripts/run-bazel-ci.sh \
      --print-failed-action-summary \
      --print-failed-test-logs \
      -- \
      test \
      --test_tag_filters=-argument-comment-lint \
      --test_verbose_timeout_warnings \
      --build_metadata=COMMIT_SHA=${GITHUB_SHA} \
      --build_metadata=TAG_windows_native_main=true \
      -- \
      //... \
      -//third_party/v8:all
    ```
    
    ## Research Notes
    
    The existing macOS Bazel CI config already uses the model we want here:
    build actions run remotely with `--strategy=remote`, but `TestRunner`
    actions execute on the macOS runner. This PR mirrors that pattern for
    Windows with `--strategy=TestRunner=local`.
    
    The important Bazel detail is that `rules_rs` is already targeting
    `x86_64-pc-windows-gnullvm` for Windows Bazel PR tests. This PR changes
    where the build actions execute; it does not switch the Bazel PR test
    target to Cargo, `cargo-nextest`, or the MSVC release target.
    
    Cargo release builds differ from this Bazel path for V8: the normal
    Windows Cargo release target is MSVC, and `rusty_v8` publishes prebuilt
    Windows MSVC `.lib.gz` archives. The Bazel PR path targets
    `windows-gnullvm`; `rusty_v8` does not publish a prebuilt Windows
    GNU/gnullvm archive, so this PR builds that archive in-tree. That
    Linux-RBE-built gnullvm archive currently crashes in direct V8/code-mode
    smoke tests, which is why the workflow keeps native Windows coverage on
    `main`.
    
    The less obvious Bazel detail is test wrapper selection. Bazel chooses
    the Windows test wrapper (`tw.exe`) from the test action execution
    platform, not merely from the Rust target triple. The outer
    `workspace_root_test` therefore declares the default test toolchain and
    uses the bridge toolchain above so the test action executes on Windows
    while its inner Rust binary is built for gnullvm.
    
    The V8 investigation exposed a Windows-client gotcha: even when an
    action execution platform is Linux RBE, Bazel can still derive the
    genrule shell path from the Windows client. That produced remote
    commands trying to run `C:\Program Files\Git\usr\bin\bash.exe` on Linux
    workers. The wrapper now passes `--shell_executable=/bin/bash` with
    `--host_platform=//:rbe` for the Windows cross path.
    
    The same Windows-client/Linux-RBE boundary also affected
    `third_party/v8:binding_cc`: a multiline genrule command can carry CRLF
    line endings into Linux remote bash, which failed as `$'\r'`. That
    genrule now keeps the `sed` command on one physical shell line while
    using an explicit Starlark join so the shell arguments stay readable.
    
    ## Verification
    
    Local checks included:
    
    ```shell
    bash -n .github/scripts/run-bazel-ci.sh
    bash -n workspace_root_test_launcher.sh.tpl
    ruby -e "require %q{yaml}; YAML.load_file(%q{.github/workflows/bazel.yml}); puts %q{ok}"
    RUNNER_OS=Linux ./scripts/list-bazel-clippy-targets.sh
    RUNNER_OS=Windows ./scripts/list-bazel-clippy-targets.sh
    RUNNER_OS=Linux ./tools/argument-comment-lint/list-bazel-targets.sh
    RUNNER_OS=Windows ./tools/argument-comment-lint/list-bazel-targets.sh
    ```
    
    The Linux clippy and argument-comment target lists contain zero
    `*-windows-cross-bin` labels, while the Windows lists still include 47
    Windows-cross internal test binaries.
    
    CI evidence:
    
    - Baseline native Windows Bazel test on `main`: success in about 28m12s,
    https://github.com/openai/codex/actions/runs/25206257208/job/73907325959
    - Green Windows-cross Bazel run on the split PR before adding the
    main-only native leg: Windows test 9m16s, Windows release verify 5m10s,
    Windows clippy 4m43s,
    https://github.com/openai/codex/actions/runs/25231890068
    - The latest SHA adds the explicit PR-vs-main tradeoff in `bazel.yml`;
    CI is rerunning on that focused diff.
    
    ## Follow-Up
    
    A subsequent PR should investigate making a cross-built Windows binary
    work with V8/code-mode enabled. Likely options are either making the
    Linux-RBE-built `windows-gnullvm` V8 archive correct at runtime, or
    evaluating whether a Bazel MSVC target/toolchain can reuse the same
    prebuilt MSVC `rusty_v8` archive shape that Cargo release builds already
    use.
  • Prune unused code-mode globals (#20542)
    Hide Atomics, SharedArrayBuffer, and WebAssembly from the code-mode
    runtime since the harness does not expose worker support or need those
    APIs.
  • Surface multi-environment choices in environment context (#20646)
    ## Why
    The model needs a way to see which environments are available during a
    multi-environment turn without changing the legacy single-environment
    prompt surface or pulling replay/persistence changes into the same
    review.
    
    ## Stack
    1. https://github.com/openai/codex/pull/20646 - `EnvironmentContext`
    rendering for selected environments (this PR)
    2. https://github.com/openai/codex/pull/20669 - selected-environment
    ownership and tool config prep
    3. https://github.com/openai/codex/pull/20647 - process-tool
    `environment_id` routing
    
    ## What Changed
    - extend `environment_context` so multi-environment turns render an
    `<environments>` block with the selected environment ids and cwd values
    - keep zero- and single-environment turns on the existing cwd-only
    render path
    - keep replay and persistence paths on the legacy surface for now so
    this PR stays scoped to live prompt rendering
    - add focused coverage in
    `codex-rs/core/src/context/environment_context_tests.rs`
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Clear live hook rows when turns finalize (#20674)
    # Why
    
    When a user interrupts a turn while a hook is still running, the normal
    turn status is cleared but the separate live hook row can remain visible
    as `Running` because the TUI may never receive a matching
    `HookCompleted` event before cancellation. Once the turn itself is
    finalized, that turn-scoped live state should not remain on screen.
    
    # What
    
    - clear any still-live `active_hook_cell` during turn finalization
    - add a regression snapshot covering an interrupted turn with a visible
    `PreToolUse` hook row
    
    # Testing
    
    - `cargo test -p codex-tui interrupted_turn_clears_visible_running_hook`
    - attempted `cargo test -p codex-tui` (currently aborts on unrelated
    existing stack overflow in
    `app::tests::discard_side_thread_removes_agent_navigation_entry`)
  • Use the 2025-06-18 elicitation capability shape (#20562)
    # Why
    
    Codex currently negotiates MCP `2025-06-18`, where the client
    elicitation capability is represented as an empty object. We were still
    serializing `capabilities.elicitation.form`, which belongs to the later
    capability shape and can cause strict `2025-06-18` servers to reject
    `initialize` with an unrecognized-field error.
    
    This keeps the handshake aligned with the protocol version Codex
    actually negotiates and fixes the compatibility regression tracked in
    #17492.
    
    # What
    
    - Serialize the client elicitation capability as `elicitation: {}` for
    `2025-06-18`.
    - Keep elicitation advertised for both Codex Apps and custom MCP
    servers.
    - Tighten regression coverage so the unit test asserts both the Rust
    value and the serialized wire shape.
    - Add an app-server integration test that round-trips a form elicitation
    from a custom MCP server; the existing connector round-trip continues to
    cover the connector path.
    
    # Verification
    
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-app-server mcp_server_elicitation_round_trip`
    - `cargo test -p codex-app-server
    mcp_server_tool_call_round_trips_elicitation`
    
    # Next steps
    
    - Decide whether `tool_call_mcp_elicitation=false` should also suppress
    capability advertisement during `initialize`.
    - Revisit `form` / `url` capability advertisement when Codex is ready to
    negotiate MCP `2025-11-25`, which defines that newer shape.
  • [codex] Emit image view as core item (#20512)
    ## Why
    
    Image-view results should be represented as a core-produced turn item
    instead of being reconstructed by app-server. At the same time, existing
    rollout/history paths still understand the legacy `ViewImageToolCall`
    event, so this keeps that event as compatibility output generated from
    the new item lifecycle.
    
    ## What changed
    
    - Added `TurnItem::ImageView` to `codex-protocol`.
    - Emitted image-view item start/completion directly from the core
    `view_image` handler.
    - Kept `ViewImageToolCall` as a legacy event and generate it from
    completed `TurnItem::ImageView` items.
    - Kept `thread_history.rs` on the legacy `ViewImageToolCall` replay
    path, with `ImageView` item lifecycle events ignored there.
    - Updated app-server protocol conversion, rollout persistence, and
    affected exhaustive event matches for the new item plus legacy fan-out
    shape.
    
    ## Verification
    
    - `cargo test -p codex-protocol -p codex-app-server-protocol -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server -p
    codex-app-server --lib`
    - `cargo test -p codex-core --test all
    view_image_tool_attaches_local_image`
    - `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-rollout -p codex-rollout-trace -p
    codex-mcp-server`
    - `git diff --check`
  • /plugins: add marketplace upgrade flow (#20478)
    This PR adds marketplace upgrade to the `/plugins` menu so users can
    update configured marketplaces. It adds a `Ctrl+U` shortcut on eligible
    marketplace tabs, a loading state, and the app-server request flow
    needed to perform `marketplace/upgrade`. After a successful upgrade, the
    TUI refreshes plugin data, plugin mentions, and user config so updated
    marketplace contents show up across the menu and other plugin surfaces.
    It also preserves the current marketplace tab on no-op and failure paths
    and surfaces backend error details directly in the TUI.
    
    - Add a `Ctrl+U` upgrade option for user-configured marketplace tabs in
    `/plugins`
    - Show the upgrade footer hint only on upgradeable marketplace tabs
    - Show a loading state during `marketplace/upgrade`
    - Surface already-up-to-date and per-marketplace failure results from
    the backend
    - Refresh plugin data, plugin mentions, and user config after successful
    upgrades
    - Add tests and snapshot updates for the shortcut flow, loading state,
    and failure messaging
    
    Steps to test:
    1. Add a `/plugin` marketplace to Codex TUI.
    2. Open `/plugins`, move to that marketplace tab, and confirm the footer
    shows `Ctrl+U` to upgrade.
    3. Press `Ctrl+U` and confirm the popup switches into an upgrade loading
    state.
    4. When the request finishes, confirm you see the expected result:
    updated marketplace contents on success, an already-up-to-date message
    on no-op, or backend error details on failure. On no-op or failure,
    confirm the popup stays on the same marketplace tab.
  • fix: reduce ConfigBuilder::build stack usage (#20650)
    ## Why
    
    `ConfigBuilder::build` performs a large amount of async config loading.
    Leaving that entire future on the caller stack makes config startup more
    fragile on small runtime worker stacks.
    
    ## What changed
    
    - keep `ConfigBuilder::build` as a thin wrapper that boxes the
    config-loading future before awaiting it
    - move the existing implementation into a private `build_inner` method
    so the large async state machine lives on the heap instead of the
    runtime thread stack
    
    ## Testing
    
    - Not run locally
  • fix(tui): restore alt-enter newline alias (#20535)
    Fixes https://github.com/openai/codex/issues/20501
    
    ## Summary
    - add Alt+Enter to the built-in editor newline aliases
    - update keymap tests that used Alt+Enter as a custom submit binding now
    that it conflicts with newline
    - refresh the keymap action-menu snapshot fixture
    
    ## Test Plan
    - `just fmt`
    - `cargo test -p codex-tui keymap::tests`
    - `cargo test -p codex-tui bottom_pane::textarea::tests`
    - `cargo test -p codex-tui keymap_setup::tests`
    - `cargo test -p codex-tui`
    - `cargo insta pending-snapshots`
    - `git diff --check`
    - `just argument-comment-lint`
  • Use selected turn environments for runtime context (#20281)
    ## Summary
    - make selected turn environments the source of truth for session
    runtime cwd and MCP runtime environment selection
    - keep local/no-selection fallback behavior intact
    - add coverage for duplicate selected environments, cwd resolution, and
    MCP runtime environment selection
    
    ## Validation
    - git diff --check
    - rustfmt was run on touched Rust files during the implementation
    workflow
    
    CI should provide the full Bazel/test signal.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Migrate loaded thread/read history to ThreadStore (#20486)
    ## Summary
    
    - Route loaded `thread/read` + `includeTurns` through
    `CodexThread::load_history` / ThreadStore history instead of direct
    rollout JSONL reads.
    - Add an in-memory ThreadStore regression test covering loaded
    `thread/read includeTurns` without a local rollout path.
  • deprecate legacy notify (#20524)
    # Why
    
    `notify` is the remaining compatibility surface from the legacy hook
    implementation. The newer lifecycle hook engine now owns the active hook
    system, so we should start steering users away from adding new `notify`
    configs before removing the old path entirely. This also adds a
    lightweight watchpoint for the deprecation so we can see how much legacy
    usage remains before the clean drop.
    
    # What
    
    - emit a startup deprecation notice when a non-empty `notify` command is
    configured
    - emit `codex.notify.configured` when a session starts with legacy
    `notify` configured
    - emit `codex.notify.run` when the legacy notify path fires after a
    completed turn
    - mark `notify` as deprecated in the config schema and repo docs
    - remove the orphaned `codex-rs/hooks/src/user_notification.rs` file
    that is no longer compiled
    - add regression coverage for the new deprecation notice
    
    # Next steps
    
    A follow-up PR can remove the legacy notify path entirely once we are
    ready for the clean drop. Before then, we can watch
    `codex.notify.configured` and `codex.notify.run` to understand the
    deprecation impact and remaining active usage. The cleanup PR should
    then delete the `notify` config field, the `legacy_notify`
    implementation, the old compatibility dispatch types and callsites that
    only exist for the legacy path, and the remaining compatibility
    docs/tests.
    
    # Testing
    
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-config`
    - `cargo test -p codex-core emits_deprecation_notice_for_notify`