Commit Graph

4843 Commits

  • Wire remote app-server auth through the client (#14853)
    For app-server websocket auth, support the two server-side mechanisms
    from
    PR #14847:
    
    - `--ws-auth capability-token --ws-token-file /abs/path`
    - `--ws-auth signed-bearer-token --ws-shared-secret-file /abs/path`
      with optional `--ws-issuer`, `--ws-audience`, and
      `--ws-max-clock-skew-seconds`
    
    On the client side, add interactive remote support via:
    
    - `--remote ws://host:port` or `--remote wss://host:port`
    - `--remote-auth-token-env <ENV_VAR>`
    
    Codex reads the bearer token from the named environment variable and
    sends it
    as `Authorization: Bearer <token>` during the websocket handshake.
    Remote auth
    tokens are only allowed for `wss://` URLs or loopback `ws://` URLs.
    
    Testing:
    - tested both auth methods manually to confirm connection success and
    rejection for both auth types
  • Fix quoted command rendering in tui_app_server (#15825)
    When `tui_app_server` is enabled, shell commands in the transcript
    render as fully quoted invocations like `/bin/zsh -lc "..."`. The
    non-app-server TUI correctly shows the parsed command body.
    
    Root cause:
    The app-server stores `ThreadItem::CommandExecution.command` as a
    shell-quoted string. When `tui_app_server` bridges that item back into
    the exec renderer, it was passing `vec![command]` unchanged instead of
    splitting the string back into argv. That prevented
    `strip_bash_lc_and_escape()` from recognizing the shell wrapper, so the
    renderer displayed the wrapper literally.
    
    Solution:
    Add a shared command-string splitter that round-trips shell-quoted
    commands back into argv when it is safe to do so, while preserving
    non-roundtrippable inputs as a single string. Use that helper everywhere
    `tui_app_server` reconstructs exec commands from app-server payloads,
    including live command-execution items, replayed thread items, and exec
    approval requests. This restores the same command display behavior as
    the direct TUI path without breaking Windows-style commands that cannot
    be safely round-tripped.
  • [plugins] Flip flags on. (#15820)
    - [x] Flip flags on.
  • [hooks] add non-streaming (non-stdin style) shell-only PostToolUse support (#15531)
    CHAINED PR - note that base is eternal/hooks-pretooluse-bash, not main
    -- so the following PR should be first
    
    Matching post-tool hook to the pre-tool functionality here:
    https://github.com/openai/codex/pull/15211
    
    So, PreToolUse calls for plain shell calls, allows blocking. This
    PostToolUse call runs after the command executed
    
    example run:
    
    ```
    › as a test, run in parallel the following commands:
      - echo 'one'
      - echo '[block-pre-tool-use]'
      - echo '[block-post-tool-use]'
    
    
    ⚠ MCP startup incomplete (failed: notion, linear)
    
    • Cruising through those three commands in parallel now, and I’ll share the exact outputs right after
      they land.
    
    • Running PreToolUse hook: checking the observatory runes
    
    • Running PreToolUse hook: checking the observatory runes
    
    • Running PreToolUse hook: checking the observatory runes
    
    PreToolUse hook (blocked)
      warning: wizard-tower PreToolUse demo blocked a Bash command on purpose.
      feedback: PreToolUse demo blocked the command. Remove [block-pre-tool-use] to continue.
    
    PreToolUse hook (completed)
      warning: wizard-tower PreToolUse demo inspected Bash: echo 'one'
    
    PreToolUse hook (completed)
      warning: wizard-tower PreToolUse demo inspected Bash: echo '[block-post-tool-use]'
    
    • Ran echo '[block-post-tool-use]'
      └ [block-post-tool-use]
    
    • Ran echo 'one'
      └ one
    
    • Running PostToolUse hook: reading the observatory aftermath
    
    • Running PostToolUse hook: reading the observatory aftermath
    
    PostToolUse hook (blocked)
      warning: wizard-tower PostToolUse demo blocked the Bash follow-through on purpose.
      feedback: PostToolUse demo blocked the result after execution. Remove [block-post-tool-use] to
    continue.
    
    PostToolUse hook (completed)
      warning: wizard-tower PostToolUse demo inspected Bash: echo 'one'
      hook context: PostToolUse demo saw the Bash result. Response preview: one
    
    ──────────────────────────────────────────────────────────────────────────────────────────────────────
    
    • Ran them in parallel. Little wave report:
    
      - echo 'one' completed and returned one.
      - echo '[block-pre-tool-use]' was blocked before execution by the PreToolUse hook.
      - echo '[block-post-tool-use]' ran, but its result was blocked afterward by the PostToolUse hook.
    
      The hook messages were:
    
      - PreToolUse demo blocked the command. Remove [block-pre-tool-use] to continue.
      - PostToolUse demo blocked the result after execution. Remove [block-post-tool-use] to continue.
    
    
    › for the blocked messages (correctly blocked in this test) did the tool output land back into
      context?
    
    
    • Yeah, from this run:
    
      - PreToolUse block: the command itself did not execute, so no command output landed in context. Only
        the block message did.
      - PostToolUse block: the command did execute, but the actual stdout did not come back into context.
        Only the post-tool block message did.
    
      So the blocked notifications landed in context, but the blocked command payloads themselves did not.
      Nice clean guardrail, aloha.
    ```
  • [mcp] Improve custom MCP elicitation (#15800)
    - [x] Support don't ask again for custom MCP tool calls.
    - [x] Don't run arc in yolo mode.
    - [x] Run arc for custom MCP tools in always allow mode.
  • app-server: Organize app-server to allow more transports (#15810)
    Make `run_main_with_transport` slightly more flexible by consolidating
    logic spread across stdio and websocket transports.
  • Add ReloadUserConfig to tui_app_server (#15806)
    - Adds ReloadUserConfig to `tui_app_server`
  • Add cached environment manager for exec server URL (#15785)
    Add environment manager that is a singleton and is created early in
    app-server (before skill manager, before config loading).
    
    Use an environment variable to point to a running exec server.
  • TUI plugin menu polish (#15802)
    - Add "OpenAI Curated" display name for `openai-curated` marketplace
    - Hide /apps menu
    - Change app install phase display text
  • Add MCP tool call spans (#15659)
    ## Summary
    - add an explicit `mcp.tools.call` span around MCP tool execution in
    core
    - keep MCP span validation local to `mcp_tool_call_tests` instead of
    broadening the integration test suite
    - inline the turn/session correlation fields directly in the span
    initializer
    
    ## Included Changes
    - `codex-rs/core/src/mcp_tool_call.rs`: wrap the existing MCP tool call
    in `mcp.tools.call` and inline `conversation.id`, `session.id`, and
    `turn.id` in the span initializer
    - `codex-rs/core/src/mcp_tool_call_tests.rs`: assert the MCP span
    records the expected correlation and server fields
    
    ## Testing
    - `cargo test -p codex-core`
    - `just fmt`
    
    ## Notes
    - `cargo test -p codex-core` still hits existing unrelated failures in
    guardian-config tests and the sandboxed JS REPL `mktemp` test
    - metric work moved to stacked PR #15792
    - transport-level RMCP spans and trace propagation remain in stacked PR
    #15792
    - full workspace `cargo test` was not run
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Avoid duplicate auth refreshes in getAuthStatus (#15798)
    I've seen several intermittent failures of
    `get_auth_status_returns_token_after_proactive_refresh_recovery` today.
    I investigated, and I found a couple of issues.
    
    First, `getAuthStatus(refreshToken=true)` could refresh twice in one
    request: once via `refresh_token_if_requested()` and again via the
    proactive refresh path inside `auth_manager.auth()`. In the
    permanent-failure case this produced an extra `/oauth/token` call and
    made the app-server auth tests flaky. Use `auth_cached()` after an
    explicit refresh request so the handler reuses the post-refresh auth
    state instead of immediately re-entering proactive refresh logic. Keep
    the existing proactive path for `refreshToken=false`.
    
    Second, serialize auth refresh attempts in `AuthManager` have a
    startup/request race. One proactive refresh could already be in flight
    while a `getAuthStatus(refreshToken=false)` request entered
    `auth().await`, causing a second `/oauth/token` call before the first
    failure or refresh result had been recorded. Guarding the refresh flow
    with a single async lock makes concurrent callers share one refresh
    result, which prevents duplicate refreshes and stabilizes the
    proactive-refresh auth tests.
  • Extract codex-core-skills crate (#15749)
    ## Summary
    - move skill loading and management into codex-core-skills
    - leave codex-core with the thin integration layer and shared wiring
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(tui_app_server): preserve transcript events under backpressure (#15759)
    ## TL;DR
    
    When running codex with `-c features.tui_app_server=true` we see
    corruption when streaming large amounts of data. This PR marks other
    event types as _critical_ by making them _must-deliver_.
    
    ## Problem
    
    When the TUI consumer falls behind the app-server event stream, the
    bounded `mpsc` channel fills up and the forwarding layer drops events
    via `try_send`. Previously only `TurnCompleted` was marked as
    must-deliver. Streamed assistant text (`AgentMessageDelta`) and the
    authoritative final item (`ItemCompleted`) were treated as droppable —
    the same as ephemeral command output deltas. Because the TUI renders
    markdown incrementally from these deltas, dropping any of them produces
    permanently corrupted or incomplete paragraphs that persist for the rest
    of the session.
    
    ## Mental model
    
    The app-server event stream has two tiers of importance:
    
    1. **Lossless (transcript + terminal):** Events that form the
    authoritative record of what the assistant said or that signal turn
    lifecycle transitions. Losing any of these corrupts the visible output
    or leaves surfaces waiting forever. These are: `AgentMessageDelta`,
    `PlanDelta`, `ReasoningSummaryTextDelta`, `ReasoningTextDelta`,
    `ItemCompleted`, and `TurnCompleted`.
    
    2. **Best-effort (everything else):** Ephemeral status events like
    `CommandExecutionOutputDelta` and progress notifications. Dropping these
    under load causes cosmetic gaps but no permanent corruption.
    
    The forwarding layer uses `try_send` for best-effort events (dropping on
    backpressure) and blocking `send().await` for lossless events (applying
    back-pressure to the producer until the consumer catches up).
    
    ## Non-goals
    
    - Eliminating backpressure entirely. The bounded queue is intentional;
    this change only widens the set of events that survive it.
    - Changing the event protocol or adding new notification types.
    - Addressing root causes of consumer slowness (e.g. TUI render cost).
    
    ## Tradeoffs
    
    Blocking on transcript events means a slow consumer can now stall the
    producer for the duration of those events. This is acceptable because:
    (a) the alternative is permanently broken output, which is worse; (b)
    the consumer already had to keep up with `TurnCompleted` blocking sends;
    and (c) transcript events arrive at model-output speed, not burst speed,
    so sustained saturation is unlikely in practice.
    
    ## Architecture
    
    Two parallel changes, one per transport:
    
    - **In-process path** (`lib.rs`): The inline forwarding logic was
    extracted into `forward_in_process_event`, a standalone async function
    that encapsulates the lag-marker / must-deliver / try-send decision
    tree. The worker loop now delegates to it. A new
    `server_notification_requires_delivery` function (shared `pub(crate)`)
    centralizes the notification classification.
    
    - **Remote path** (`remote.rs`): The local `event_requires_delivery` now
    delegates to the same shared `server_notification_requires_delivery`,
    keeping both transports in sync.
    
    ## Observability
    
    No new metrics or log lines. The existing `warn!` on event drops
    continues to fire for best-effort events. Lossless events that block
    will not produce a log line (they simply wait).
    
    ## Tests
    
    - `event_requires_delivery_marks_transcript_and_terminal_events`: unit
    test confirming the expanded classification covers `AgentMessageDelta`,
    `ItemCompleted`, `TurnCompleted`, and excludes
    `CommandExecutionOutputDelta` and `Lagged`.
    -
    `forward_in_process_event_preserves_transcript_notifications_under_backpressure`:
    integration-style test that fills a capacity-1 channel, verifies a
    best-effort event is dropped (skipped count increments), then sends
    lossless transcript events and confirms they all arrive in order with
    the correct lag marker preceding them.
    - `remote_backpressure_preserves_transcript_notifications`: end-to-end
    test over a real websocket that verifies the remote transport preserves
    transcript events under the same backpressure scenario.
    - `event_requires_delivery_marks_transcript_and_disconnect_events`
    (remote): unit test confirming the remote-side classification covers
    transcript events and `Disconnected`.
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • feat: add websocket auth for app-server (#14847)
    ## Summary
    This change adds websocket authentication at the app-server transport
    boundary and enforces it before JSON-RPC `initialize`, so authenticated
    deployments reject unauthenticated clients during the websocket
    handshake rather than after a connection has already been admitted.
    
    During rollout, websocket auth is opt-in for non-loopback listeners so
    we do not break existing remote clients. If `--ws-auth ...` is
    configured, the server enforces auth during websocket upgrade. If auth
    is not configured, non-loopback listeners still start, but app-server
    logs a warning and the startup banner calls out that auth should be
    configured before real remote use.
    
    The server supports two auth modes: a file-backed capability token, and
    a standard HMAC-signed JWT/JWS bearer token verified with the
    `jsonwebtoken` crate, with optional issuer, audience, and clock-skew
    validation. Capability tokens are normalized, hashed, and compared in
    constant time. Short shared secrets for signed bearer tokens are
    rejected at startup. Requests carrying an `Origin` header are rejected
    with `403` by transport middleware, and authenticated clients present
    credentials as `Authorization: Bearer <token>` during websocket upgrade.
    
    ## Validation
    - `cargo test -p codex-app-server transport::auth`
    - `cargo test -p codex-cli app_server_`
    - `cargo clippy -p codex-app-server --all-targets -- -D warnings`
    - `just bazel-lock-check`
    
    Note: in the broad `cargo test -p codex-app-server
    connection_handling_websocket` run, the touched websocket auth cases
    passed, but unrelated Unix shutdown tests failed with a timeout in this
    environment.
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • [apps][tool_suggest] Remove tool_suggest's dependency on tool search. (#14856)
    - [x] Remove tool_suggest's dependency on tool search.
  • fix(tui_app_server): fix remote subagent switching and agent names (#15513)
    ## TL;DR
    
    This PR changes the `tui_app_server` _path_ in the following ways:
    
    - add missing feature to show agent names (shows only UUIDs today) 
    - add `Cmd/Alt+Arrows` navigation between agent conversations
    
    ## Problem
    
    When the TUI connects to a remote app server, collab agent tool-call
    items (spawn, wait, delegate, etc.) render thread UUIDs instead of
    human-readable agent names because the `ChatWidget` never receives
    nickname/role metadata for receiver threads. Separately, keyboard
    next/previous agent navigation silently does nothing when the local
    `AgentNavigationState` cache has not yet been populated with subagent
    threads that the remote server already knows about.
    
    Both issues share a root cause: in the remote (app-server) code path the
    TUI never proactively fetches thread metadata. In the local code path
    this metadata arrives naturally via spawn events the TUI itself
    orchestrates, but in the remote path those events were processed by a
    different client and the TUI only sees the resulting collab tool-call
    notifications.
    
    ## Mental model
    
    Collab agent tool-call notifications reference receiver threads by id,
    but carry no nickname or role. The TUI needs that metadata in two
    places:
    
    1. **Rendering** -- `ChatWidget` converts `CollabAgentToolCall` items
    into history cells. Without metadata, agent status lines show raw UUIDs.
    2. **Navigation** -- `AgentNavigationState` tracks known threads for the
    `/agent` picker and keyboard cycling. Without entries for remote
    subagents, next/previous has nowhere to go.
    
    This change closes the gap with two complementary strategies:
    
    - **Eager hydration**: when any notification carries
    `receiver_thread_ids`, the TUI fetches metadata (`thread/read`) for
    threads it has not yet cached before the notification is rendered.
    - **Backfill on thread switch**: when the user resumes, forks, or starts
    a new app-server thread, the TUI fetches the full `thread/loaded/list`,
    walks the parent-child spawn tree, and registers every descendant
    subagent in both the navigation cache and the `ChatWidget` metadata map.
    
    A new `collab_agent_metadata` side-table in `ChatWidget` stores
    nickname/role keyed by `ThreadId`, kept in sync by `App` whenever it
    calls `upsert_agent_picker_thread`. The `replace_chat_widget` helper
    re-seeds this map from `AgentNavigationState` so that thread switches
    (which reconstruct the widget) do not lose previously discovered
    metadata.
    
    ## Non-goals
    
    - This change does not alter the local (non-app-server) collab code
    path. That path already receives metadata via spawn events and is
    unaffected.
    - No new protocol messages are introduced. The change uses existing
    `thread/read` and `thread/loaded/list` RPCs.
    - No changes to how `AgentNavigationState` orders or cycles through
    threads. The traversal logic is unchanged; only the population of
    entries is extended.
    
    ## Tradeoffs
    
    - **Extra RPCs on notification path**:
    `hydrate_collab_agent_metadata_for_notification` issues a `thread/read`
    for each unknown receiver thread before the notification is forwarded to
    rendering. This adds latency on the notification path but only fires
    once per thread (the result is cached). The alternative -- rendering
    first and backfilling names later -- would cause visible flicker as
    UUIDs are replaced with names.
    - **Backfill fetches all loaded threads**:
    `backfill_loaded_subagent_threads` fetches the full loaded-thread list
    and walks the spawn tree even when the user may only care about one
    subagent. This is simple and correct but O(loaded_threads) per thread
    switch. For typical session sizes this is negligible; it could become a
    concern for sessions with hundreds of subagents.
    - **Metadata duplication**: agent nickname/role is now stored in both
    `AgentNavigationState` (for picker/label) and
    `ChatWidget::collab_agent_metadata` (for rendering). The two are kept in
    sync through `upsert_agent_picker_thread` and `replace_chat_widget`, but
    there is no compile-time enforcement of this coupling.
    
    ## Architecture
    
    ### New module: `app::loaded_threads`
    
    Pure function `find_loaded_subagent_threads_for_primary` that takes a
    flat list of `Thread` objects and a primary thread id, then walks the
    `SessionSource::SubAgent` parent-child edges to collect all transitive
    descendants. Returns a sorted vec of `LoadedSubagentThread` (thread_id +
    nickname + role). No async, no side effects -- designed for unit
    testing.
    
    ### New methods on `App`
    
    | Method | Purpose |
    |--------|---------|
    | `collab_receiver_thread_ids` | Extracts `receiver_thread_ids` from
    `ItemStarted` / `ItemCompleted` collab notifications |
    | `hydrate_collab_agent_metadata_for_notification` | Fetches and caches
    metadata for unknown receiver threads before a notification is rendered
    |
    | `backfill_loaded_subagent_threads` | Bulk-fetches all loaded threads
    and registers descendants of the primary thread |
    | `adjacent_thread_id_with_backfill` | Attempts navigation, falls back
    to backfill if the cache has no adjacent entry |
    | `replace_chat_widget` | Replaces the widget and re-seeds its metadata
    map from `AgentNavigationState` |
    
    ### New state in `ChatWidget`
    
    `collab_agent_metadata: HashMap<ThreadId, CollabAgentMetadata>` -- a
    lookup table that rendering functions consult to attach human-readable
    names to collab tool-call items. Populated externally by `App` via
    `set_collab_agent_metadata`.
    
    ### New method on `AppServerSession`
    
    `thread_loaded_list` -- thin wrapper around
    `ClientRequest::ThreadLoadedList`.
    
    ## Observability
    
    - `tracing::warn` on invalid thread ids during hydration and backfill.
    - `tracing::warn` on failed `thread/read` or `thread/loaded/list` RPCs
    (with thread id and error).
    - No new metrics or feature flags.
    
    ## Tests
    
    -
    **`loaded_threads::tests::finds_loaded_subagent_tree_for_primary_thread`**
    -- unit test for the spawn-tree walk: verifies child and grandchild are
    included, unrelated threads are excluded, and metadata is carried
    through.
    -
    **`app::tests::replace_chat_widget_reseeds_collab_agent_metadata_for_replay`**
    -- integration test that creates a `ChatWidget`, replaces it via
    `replace_chat_widget`, replays a collab wait notification, and asserts
    the rendered history cell contains the agent name rather than a UUID.
    - **Updated snapshot** `app_server_collab_wait_items_render_history` --
    the existing collab wait rendering test now sets metadata before sending
    notifications, so the snapshot shows `Robie [explorer]` / `Ada
    [reviewer]` instead of raw thread ids.
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • Clarify codex_home base for MDM path resolution (#15707)
    ## Summary
    
    Add the follow up code comment Michael asked for at the MDM
    `managed_config_from_mdm` - a follow up from
    https://github.com/openai/codex/pull/15351.
    
    ## Validation
    
    1. `cargo fmt --all --check`
    2. `cargo test -p codex-core
    managed_preferences_expand_home_directory_in_workspace_write_roots --
    --nocapture`
    3. `cargo test -p codex-core
    write_value_succeeds_when_managed_preferences_expand_home_directory_paths
    -- --nocapture`
    4. `./tools/argument-comment-lint/run-prebuilt-linter.sh -p codex-core`
  • Extract codex-analytics crate (#15748)
    ## Summary
    - move the analytics events client into codex-analytics
    - update codex-core and app-server callsites to use the new crate
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Extract codex-plugin crate (#15747)
    ## Summary
    - extract plugin identifiers and load-outcome types into codex-plugin
    - update codex-core to consume the new plugin crate
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Extract codex-utils-plugins crate (#15746)
    ## Summary
    - extract shared plugin path and manifest helpers into
    codex-utils-plugins
    - update codex-core to consume the utility crate
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add non-interactive resume filter option (#15339)
    ## Summary
    - add `codex resume --include-non-interactive` to include
    non-interactive sessions in the picker and `--last`
    - keep current-provider and cwd filtering behavior unchanged
    - replace the picker API boolean with a `SessionSourceFilter` enum to
    avoid a boolean trap
    
    ## Tests
    - `cargo test -p codex-cli`
    - `cargo test -p codex-tui`
    - `just fmt`
    - `just fix -p codex-cli`
    - `just fix -p codex-tui`
  • Extract codex-instructions crate (#15744)
    ## Summary
    - extract instruction fragment and user-instruction types into
    codex-instructions
    - update codex-core to consume the new crate
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: add multi-thread log query (#15776)
    Required for multi-agent v2
  • fix(tui): avoid duplicate live reasoning summaries (#15758)
    ## TL;DR
    
    Fix duplicated reasoning summaries in `tui_app_server`.
    
    <img width="1716" height="912" alt="image"
    src="https://github.com/user-attachments/assets/6362f25a-ab1c-4a01-bf10-b5616c9428c2"
    />
    
    During live turns, reasoning text is already rendered incrementally from
    `ReasoningSummaryTextDelta`. When the same reasoning item later arrives
    via `ItemCompleted`, we should only finalize the reasoning block, not
    render the same summary again.
    
    ## What changed
    
    - only replay rendered reasoning summaries from completed
    `ThreadItem::Reasoning` items
    - kept live completed reasoning items as finalize-only
    - added a regression test covering the live streaming + completion path
    
    ## Why
    
    Without this, the first reasoning summary often appears twice in the
    transcript when `model_reasoning_summary = "detailed"` and
    `features.tui_app_server = true`.
  • feat: rendering library v1 (#15778)
    The goal will be to replace askama
  • Use AbsolutePathBuf for cwd state (#15710)
    Migrate `cwd` and related session/config state to `AbsolutePathBuf` so
    downstream consumers consistently see absolute working directories.
    
    Add test-only `.abs()` helpers for `Path`, `PathBuf`, and `TempDir`, and
    update branch-local tests to use them instead of
    `AbsolutePathBuf::try_from(...)`.
    
    For the remaining TUI/app-server snapshot coverage that renders absolute
    cwd values, keep the snapshots unchanged and skip the Windows-only cases
    where the platform-specific absolute path layout differs.
  • chore: remove grep_files handler (#15775)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(core): default approval behavior for mcp missing annotations (#15519)
    - Changed `requires_mcp_tool_approval` to apply MCP spec defaults when
    annotations are missing.
    - Unannotated tools now default to:
      - `readOnlyHint = false`
      - `destructiveHint = true`
      - `openWorldHint = true`
    - This means unannotated MCP tools now go through approval/ARC
    monitoring instead of silently bypassing it.
    - Explicitly read-only tools still skip approval unless they are also
    explicitly marked destructive.
    
    **Previous behavior**
    Failed open for missing annotations, which was unsafe for custom MCP
    tools that omitted or forgot annotations.
    
    ---------
    
    Co-authored-by: colby-oai <228809017+colby-oai@users.noreply.github.com>
  • Update plugin creator skill. (#15734)
    Add support for home-local plugin + fix policy.
  • [plugins] Add a flag for tool search. (#15722)
    - [x] Add a flag for tool search.
  • Fix stale turn steering fallback in tui_app_server (#15714)
    This PR adds code to recover from a narrow app-server timing race where
    a follow-up can be sent after the previous turn has already ended but
    before the TUI has observed that completion.
    
    Instead of surfacing turn/steer failed: no active turn to steer, the
    client now treats that as a stale active-turn cache and falls back to
    starting a fresh turn, matching the intended submit behavior more
    closely. This is similar to the strategy employed by other app server
    clients (notably, the IDE extension and desktop app).
    
    This race exists because the current app-server API makes the client
    choose between two separate RPCs, turn/steer and turn/start, based on
    its local view of whether a turn is still active. That view is
    replicated from asynchronous notifications, so it can be stale for a
    brief window. The server may already have ended the turn while the
    client still believes it is in progress. Since the choice is made
    client-side rather than atomically on the server, tui_app_server can
    occasionally send turn/steer for a turn that no longer exists.
  • fix: support split carveouts in windows restricted-token sandbox (#14172)
    ## Summary
    - keep legacy Windows restricted-token sandboxing as the supported
    baseline
    - support the split-policy subset that restricted-token can enforce
    directly today
    - support full-disk read, the same writable root set as legacy
    `WorkspaceWrite`, and extra read-only carveouts under those writable
    roots via additional deny-write ACLs
    - continue to fail closed for unsupported split-only shapes, including
    explicit unreadable (`none`) carveouts, reopened writable descendants
    under read-only carveouts, and writable root sets that do not match the
    legacy workspace roots
    
    ## Example
    Given a filesystem policy like:
    
    ```toml
    ":root" = "read"
    ":cwd" = "write"
    "./docs" = "read"
    ```
    
    the restricted-token backend can keep the workspace writable while
    denying writes under `docs` by layering an extra deny-write carveout on
    top of the legacy workspace-write roots.
    
    A policy like:
    
    ```toml
    "/workspace" = "write"
    "/workspace/docs" = "read"
    "/workspace/docs/tmp" = "write"
    ```
    
    still fails closed, because the unelevated backend cannot reopen the
    nested writable descendant safely.
    
    ## Stack
    -> fix: support split carveouts in windows restricted-token sandbox
    #14172
    fix: support split carveouts in windows elevated sandbox #14568
  • [plugins] Flip on additional flags. (#15719)
    - [x] Flip on additional flags.
  • [plugins] Flip the flags. (#15713)
    - [x] Flip the `plugins` and `apps` flags.
  • Fix stale quickstart integration assertion (#15677)
    TL;DR: update the quickstart integration assertion to match the current
    example output.
    
    - replace the stale `Status:` expectation for
    `01_quickstart_constructor` with `Server:`, `Items:`, and `Text:`
    - keep the existing guard against `Server: unknown`
  • TUI plugin menu cleanup - hide app ID (#15708)
    - Hide App ID from plugin details page.
  • [app-server] Add a method to override feature flags. (#15601)
    - [x] Add a method to override feature flags globally and not just
    thread level.
  • [codex] Defer fork context injection until first turn (#15699)
    ## Summary
    - remove the fork-startup `build_initial_context` injection
    - keep the reconstructed `reference_context_item` as the fork baseline
    until the first real turn
    - update fork-history tests and the request snapshot, and add a
    `TODO(ccunningham)` for remaining nondiffable initial-context inputs
    
    ## Why
    Fork startup was appending current-session initial context immediately
    after reconstructing the parent rollout, then the first real turn could
    emit context updates again. That duplicated model-visible context in the
    child rollout.
    
    ## Impact
    Forked sessions now behave like resume for context seeding: startup
    reconstructs history and preserves the prior baseline, and the first
    real turn handles any current-session context emission.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Expand ~ in MDM workspace write roots (#15351)
    ## Summary
    - Reuse the existing config path resolver for the macOS MDM managed
    preferences layer so `writable_roots = ["~/code"]` expands the same way
    as file-backed config
    - keep the change scoped to the MDM branch in `config_loader`; the
    current net diff is only `config_loader/mod.rs` plus focused regression
    tests in `config_loader/tests.rs` and `config/service_tests.rs`
    - research note: `resolve_relative_paths_in_config_toml(...)` is already
    used in several existing configuration paths, including [CLI
    overrides](https://github.com/openai/codex/blob/74fda242d3651f0a43ec8657bdbc7bde426dce0e/codex-rs/core/src/config_loader/mod.rs#L152-L163),
    [file-backed managed
    config](https://github.com/openai/codex/blob/74fda242d3651f0a43ec8657bdbc7bde426dce0e/codex-rs/core/src/config_loader/mod.rs#L274-L285),
    [normal config-file
    loading](https://github.com/openai/codex/blob/74fda242d3651f0a43ec8657bdbc7bde426dce0e/codex-rs/core/src/config_loader/mod.rs#L311-L331),
    [project `.codex/config.toml`
    loading](https://github.com/openai/codex/blob/74fda242d3651f0a43ec8657bdbc7bde426dce0e/codex-rs/core/src/config_loader/mod.rs#L863-L865),
    and [role config
    loading](https://github.com/openai/codex/blob/74fda242d3651f0a43ec8657bdbc7bde426dce0e/codex-rs/core/src/agent/role.rs#L105-L109)
    
    ## Validation
    - `cargo fmt --all --check`
    - `cargo test -p codex-core
    managed_preferences_expand_home_directory_in_workspace_write_roots --
    --nocapture`
    - `cargo test -p codex-core
    write_value_succeeds_when_managed_preferences_expand_home_directory_paths
    -- --nocapture`
    
    ---------
    
    Co-authored-by: Michael Bolin <mbolin@openai.com>
    Co-authored-by: Michael Bolin <bolinfest@gmail.com>
  • Add legal link to TUI /plugin details (#15692)
    - Adds language and "[learn
    more](https://help.openai.com/en/articles/11487775-apps-in-chatgpt)"
    link to plugin details pages.
    -  Message is hidden when plugin is installed
    
    <img width="1970" height="498" alt="image"
    src="https://github.com/user-attachments/assets/f14330f7-661e-4860-8538-6dc9e8bbd90a"
    />
  • Remove provenance filtering in $mentions for apps and skills from plugins (#15700)
    - Removes provenance filtering in the mentions feature for apps and
    skills that were installed as part of a plugin.
    - All skills and apps for a plugin are mentionable with this change.
  • fix: keep rmcp-client env vars as OsString (#15363)
    ## Why
    
    This is a follow-up to #15360. That change fixed the `arg0` helper
    setup, but `rmcp-client` still coerced stdio transport environment
    values into UTF-8 `String`s before program resolution and process spawn.
    If `PATH` or another inherited environment value contains non-UTF-8
    bytes, that loses fidelity before it reaches `which` and `Command`.
    
    ## What changed
    
    - change `create_env_for_mcp_server()` to return `HashMap<OsString,
    OsString>` and read inherited values with `std::env::var_os()`
    - change `TransportRecipe::Stdio.env`, `RmcpClient::new_stdio_client()`,
    and `program_resolver::resolve()` to keep stdio transport env values in
    `OsString` form within `rmcp-client`
    - keep the `codex-core` config boundary stringly, but convert configured
    stdio env values to `OsString` once when constructing the transport
    - update the rmcp-client stdio test fixtures and callers to use
    `OsString` env maps
    - add a Unix regression test that verifies `create_env_for_mcp_server()`
    preserves a non-UTF-8 `PATH`
    
    ## How to verify
    
    - `cargo test -p codex-rmcp-client`
    - `cargo test -p codex-core mcp_connection_manager`
    - `just argument-comment-lint`
    
    Targeted coverage in this change includes
    `utils::tests::create_env_preserves_path_when_it_is_not_utf8`, while the
    updated stdio transport path is exercised by the existing rmcp-client
    tests that construct `RmcpClient::new_stdio_client()`.
  • app-server: Return codex home in initialize response (#15689)
    This allows clients to get enough information to interact with the codex
    skills/configuration/etc.
  • Use delayed shimmer for plugin loading headers in tui and tui_app_server (#15674)
    - Add a small delayed loading header for plugin list/detail loading
    messages in the TUI. Keep existing text for the first 1s, then show
    shimmer on the loading line.
    - Apply the same behavior in both tui and tui_app_server.
    
    
    https://github.com/user-attachments/assets/71dd35e4-7e3b-4e7b-867a-3c13dc395d3a
  • app-server: add filesystem watch support (#14533)
    ### Summary
    Add the v2 app-server filesystem watch RPCs and notifications, wire them
    through the message processor, and implement connection-scoped watches
    with notify-backed change delivery. This also updates the schema
    fixtures, app-server documentation, and the v2 integration coverage for
    watch and unwatch behavior.
    
    This allows clients to efficiently watch for filesystem updates, e.g. to
    react on branch changes.
    
    ### Testing
    - exercise watch lifecycles for directory changes, atomic file
    replacement, missing-file targets, and unwatch cleanup
  • Move string truncation helpers into codex-utils-string (#15572)
    - move the shared byte-based middle truncation logic from `core` into
    `codex-utils-string`
    - keep token-specific truncation in `codex-core` so rollout can reuse
    the shared helper in the next stacked PR
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>