Commit Graph

3265 Commits

  • feat(core): add metadata field to ResponseItem (#28355)
    ## Description
    
    This PR adds an optional `metadata` field to `ResponseItem` for
    Responses API calls. Only mechanical plumbing, no actual values
    populated and sent yet. Turns out just adding a new field to
    `ResponseItem` has quite a large blast radius already.
    
    This change is backwards compatible because `metadata` is optional and
    omitted when absent, so existing response items and rollout history
    without it still deserialize and requests that do not set it keep the
    same wire shape. For provider compatibility, we strip out `metadata`
    before non-OpenAI Responses requests so Azure and AWS Bedrock never see
    this field.
    
    My followup PR here will actually make use of it to start storing and
    passing along `turn_id`: https://github.com/openai/codex/pull/28360
    
    ## What changed
    
    - Added `ResponseItemMetadata` with optional `turn_id`, plus optional
    `metadata` on Responses API item variants and inter-agent communication.
    - Preserved item metadata through response-item rewrites such as
    truncation, missing tool-output synthesis, compaction history
    rebuilding, visible-history conversion, rollout/resume, and generated
    app-server schemas/types.
    - Strip item metadata from non-OpenAI Responses requests while
    preserving it for OpenAI-shaped requests.
    - Updated the mechanical fixture/test construction churn required by the
    new optional field.
  • core: cache the tool search handler per session (#27258)
    ## Why
    
    Tool router construction rebuilds the deferred-tool BM25 index during
    session initialization and before each sampling continuation, even when
    the searchable tool metadata is unchanged. Local profiling measured
    `append_tool_search_executor` at roughly 113 ms per continuation, making
    repeated index construction the largest measured router-building cost.
    
    ## What changed
    
    - Add a session-scoped `ToolSearchHandlerCache` so continuations and
    user turns can reuse the existing handler.
    - Key reuse on the complete ordered `Vec<ToolSearchInfo>`, rebuilding
    when searchable text, loadable tool specs, source metadata, or ordering
    changes.
    - Build handlers outside the cache lock and recheck before publishing
    them, avoiding holding the mutex during index construction.
    
    ## Verification
    
    - `cache_reuses_identical_search_infos_and_rebuilds_changed_inputs`
    covers exact cache reuse and invalidation when the ordered search
    metadata changes.
    - Local rollout profiling showed the initial router build populating the
    cache and unchanged later continuations reusing it:
      - uncached: 118 ms median across 14 spans from 3 rollouts
      - cached: 4 ms median across 12 spans from 3 rollouts
  • Add hidden Windows sandbox wrapper entrypoint (#28358)
    ## Why
    
    This is the second PR in the Windows fs-helper sandbox stack. The
    fs-helper path needs a Windows sandbox launcher that has the same
    argv-shaped contract as macOS `sandbox-exec` and `codex-linux-sandbox`,
    but this PR only introduces that hidden launcher. It does not route
    fs-helper through it yet.
    
    The hidden launcher still needs to be policy-complete before later
    direct-spawn callers use it. In particular, it has to carry the same
    Windows sandbox policy details that the existing spawn paths already
    understand: proxy enforcement, read/write root overrides, and
    deny-read/deny-write overrides.
    
    ## What Changed
    
    - Added the hidden `codex.exe --run-as-windows-sandbox` arg1 dispatch
    path.
    - Added `windows-sandbox-rs/src/wrapper.rs`, which parses the wrapper
    argv, launches the requested command through the shared Windows sandbox
    session runner from PR1, and forwards stdio.
    - Added `create_windows_sandbox_command_args_for_permission_profile()`
    so later direct-spawn callers can build the wrapper argv consistently.
    - Made the wrapper argv round-trip the full Windows sandbox policy
    surface it needs later: workspace roots, environment, permission
    profile, sandbox level, private desktop, proxy enforcement, read/write
    root overrides, and deny-read/deny-write overrides.
    - Carried `proxy_enforced` through the shared Windows session request so
    proxy-managed executions continue to use the offline/elevated sandbox
    identity.
    - Added wrapper argument round-trip coverage for the full policy fields.
    
    ## Verification
    
    - `just test -p codex-windows-sandbox windows_wrapper_args_round_trip`
    - `just test -p codex-arg0`
    - `just test -p codex-core exec::tests::windows_`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-cli`
    
    Local note: the full `just fmt` command still fails on this workstation
    in non-Rust formatter setup (`uv` cache access denied and missing
    `dotslash`/buildifier), but the Rust formatter phase completed.
  • Add Windows unified exec yield floor (#27086)
    ## Why
    
    The Windows `unified_exec` experiment regressed at the turn level in a
    way that points to premature backgrounding / extra command cycles rather
    than individual responses getting heavier:
    
    - `codex_local_tool_calls_per_turn` was up about 20.7%.
    - `codex_local_blended_tokens_per_turn` was up about 4.1%, and
    `codex_local_output_tokens_per_turn` was up about 4.0%.
    - `codex_local_response_latency_per_turn` was up about 8.3%.
    - The primary activity metrics also moved down: `codex_turns` about
    -6.6%, `codex_dau` about -1.0%, and `codex_local_hourly_active_users`
    about -3.0%.
    
    At the same time, the per-response metrics moved in the other direction:
    blended tokens per response, output tokens per response, and latency per
    response were all lower in test. That suggests the bad turn-level shape
    is largely about extra tool/model cycles, not each response being slower
    or more expensive on its own.
    
    Local Windows benchmarking showed the likely mechanism: shell-wrapped
    commands pay a large PowerShell startup/teardown tax before the actual
    command has much time to run. In the benchmark, the PowerShell wrapper
    added roughly 0.7-1.0s versus direct exec:
    
    - Windows PowerShell: about 740ms p50 / 800ms p90 overhead versus direct
    exec.
    - PowerShell 7 (`pwsh`): about 930ms p50 / 980ms p90 overhead versus
    direct exec.
    
    The model commonly asks for a 1s initial yield. On Windows, that can
    spend nearly the whole window waiting on PowerShell machinery, so
    otherwise-short commands are more likely to return as background
    sessions and require follow-up polling/tool calls.
    
    This is intentionally a temporary unlock. It gives Windows closer to the
    same useful post-shell command window as other platforms while we work
    on reducing the PowerShell tax directly, for example with persistent
    PowerShell workers or conservative direct-exec paths for commands that
    do not need shell semantics.
    
    ## What changed
    
    - Adds a Windows-only 2s floor to `unified_exec`'s initial
    `yield_time_ms` clamp.
    - Keeps larger model-requested waits unchanged, including the existing
    10s default.
    - Keeps the existing 30s max clamp.
    - Leaves non-Windows behavior unchanged.
    - Adds platform-gated tests for both the Windows floor and the
    non-Windows clamp behavior.
    
    ## Verification
    
    - `just test -p codex-core unified_exec`
  • core: let steer interrupt wait_agent (#28341)
    ## Why
    
    `wait_agent` can block for a long timeout while waiting for sub-agent
    mailbox activity. Although same-turn user steer is accepted during that
    tool call, the input remains pending until the wait returns, so an
    explicit request to change direction can appear unresponsive.
    
    ## What changed
    
    - Notify active `wait_agent` calls when user input is steered into the
    current turn.
    - Check for already-pending steer input when subscribing so input that
    races with tool startup is not missed.
    - Distinguish mailbox activity, steered input, and timeout outcomes,
    returning `Wait interrupted by new input.` for the steer path.
    - Update the `wait_agent` tool description to document the early-return
    behavior.
    
    ## Testing
    
    - `just test -p codex-core input_queue_`
    - `just test -p codex-core wait_agent`
    
    The coverage includes steer notification before and after subscription,
    plus an end-to-end test that verifies the interrupted wait result and
    steered user input are both included exactly once in the follow-up model
    request.
  • guardian: isolate review context from skills and memories (#28285)
    ## Why
    
    Guardian reviews embed the parent session transcript as untrusted
    evidence. Skill or plugin mentions in that transcript must not be
    interpreted as requests to inject more instructions into the Guardian
    request, and memory context adds unrelated model-visible context to an
    approval decision.
    
    Keeping those sources out of the nested review session makes the request
    smaller and preserves the trust boundary around the transcript being
    assessed.
    
    ## What changed
    
    - Skip skill and plugin discovery when building turns for Guardian
    reviewer sessions.
    - Disable memory context and dedicated memory tools in the derived
    Guardian configuration.
    - Extend the Guardian request-layout coverage to verify that a `$skill`
    mention remains visible only as transcript evidence while neither the
    skill body nor memory context is injected.
    - Expand the Guardian configuration test to cover the disabled memory
    settings.
    
    ## Testing
    
    - Updated the Guardian review request snapshot and assertions for skill
    and memory isolation.
    - Extended the Guardian session configuration test to cover memories.
  • [codex] preserve explicit environment cwd (#27995)
    ## Why
    
    `TurnEnvironmentSelections::new` rewrote the primary environment's
    explicit `cwd` to the legacy fallback cwd. For a remote-first selection,
    this could replace the remote working directory with a local fallback
    path and made the legacy cwd overlay authoritative over
    environment-owned state.
    
    ## What changed
    
    - Preserve every explicit environment cwd when constructing turn
    environment selections.
    - Keep `cwd`-only app-server updates compatible by rebuilding the
    default environment selections at the requested cwd.
    - Cover both explicit primary cwd preservation and cwd-only updates
    reaching the model-visible execution environment.
    
    ## Testing
    
    - `just test -p codex-core
    session_update_settings_does_not_rewrite_sticky_environment_cwds`
    - `just test -p codex-core
    environment_settings_preserve_explicit_primary_cwd`
    - `just test -p codex-app-server
    thread_settings_update_cwd_retargets_default_environment`
  • [codex] remove stale PathExt import (#28344)
    ## Why
    
    `main` fails dev-profile Cargo and Bazel Clippy builds because
    `core/src/tools/runtimes/mod_tests.rs` imports `PathExt` after its last
    use was removed. With warnings denied, that stale import prevents
    `codex-core` test targets from compiling across platforms.
    
    ## What changed
    
    Remove the unused `PathExt` import. Remaining `.abs()` calls in the
    module operate on `PathBuf` and continue to use `PathBufExt`.
    
    ## Validation
    
    - `just fmt`
    - Focused `codex-core` test compile attempted; blocked locally by disk
    exhaustion before compilation completed. The CI failure itself is the
    unused-import diagnostic this change removes.
  • avoid cloning websocket request history (#28313)
    ## Why
    
    WebSocket continuations only send the new part of a request. Checking
    whether a request could be continued was cloning the full previous
    request, the current request, and their input history.
    
    For long conversations or large tool lists, that meant copying several
    request-sized values on every continuation.
    
    ## What changed
    
    - compare the request settings by reference
    - check the previous input and server response as borrowed prefixes
    - allocate only the new input items that will be sent
    
    The reuse rules stay the same, including ignoring `client_metadata` for
    this check.
    
    The comparison is still `O(n)`, but it removes several `O(n)`
    allocations and copies. Temporary memory no longer grows by multiple
    full request sizes for each continuation.
    
    ## Performance
    
    Local rollout traces show continuation checks on turns around 260k input
    tokens. Before this change the reuse gate cloned the previous request,
    the current request, and the previous input history before deciding
    whether it could continue incrementally. After this change it borrows
    those structures and allocates only the incremental tail. For large
    continuations with a small delta, that removes roughly three
    request-sized copies from the hot path and reduces temporary memory from
    multiple full request sizes to just the new tail.
    
    ## Validation
    
    - `just test -p codex-core
    responses_websocket_v2_creates_with_previous_response_id_on_prefix`
    - `just test -p codex-core
    responses_websocket_v2_creates_without_previous_response_id_when_non_input_fields_change`
  • avoid cloning sampling request input (#28306)
    ## Why
    
    Every model request cloned the full prepared input just to keep it for
    the legacy after-agent hook. That copy gets more expensive as the
    conversation grows.
    
    ## What
    
    Move the prepared input into the sampling loop and return it with the
    result. If the request retries, keep the first input so the hook still
    sees the same data as before.
    
    This removes one `O(n)` clone per sampling request, where `n` is the
    size of the prepared input. It saves `O(n)` copy work and `O(n)`
    temporary memory.
    
    No behavior change is intended.
    
    ## Performance
    
    Local rollout traces show turns reaching roughly 260k input tokens. On
    turns of that size, this removes the only unconditional full
    prepared-input clone on the happy path. That avoids one request-sized
    allocation/copy per sampling attempt for large conversations, and the
    savings scale linearly with request size.
    
    ## Testing
    
    - `just test -p codex-core continue_after_stream_error`
    - `just fix -p codex-core`
  • linearize history output normalization (#28309)
    ## Why
    
    When we prepare the conversation history, every tool call needs a
    matching output.
    
    Before this change, we scanned the full history again for every call. In
    a tool-heavy conversation, that makes the work `O(items x calls)`, or
    `O(n^2)` in the worst case.
    
    ## What
    
    Scan the history once and collect the IDs of existing outputs. Then each
    call can check its ID with an expected `O(1)` lookup.
    
    The full normalization step is now expected `O(n)`. The output order and
    missing-output behavior stay the same.
    
    ## Performance
    
    Based on local rollout traces, one tool-heavy session reached roughly
    17,050 transcript items with about 4,292 tool-call items. On a history
    of that shape, the old `calls x items` scan does about 73.2 million
    membership checks, while the new pass does about 21.3 thousand set
    inserts/lookups. That is roughly 3.4k times less membership work in this
    normalization step.
    
    ## Validation
    
    - `just test -p codex-core normalize_` (19 passed)
  • [codex] simplify memory read metrics (#28164)
    ## Why
    
    Memory read telemetry currently reconstructs the executable shell
    command after a tool call finishes. That duplicates shell, login-policy,
    and cwd resolution owned by the tool handlers, and can diverge from the
    environment-specific command that unified exec actually ran.
    
    ## What changed
    
    - Expose the existing restricted shell-script parser directly for raw
    script text.
    - Parse `shell_command` and `exec_command` input into plain command argv
    before classifying memory reads.
    - Preserve all-or-nothing safe-command validation for multi-command
    scripts.
    - Remove cwd resolution, shell selection, and the unnecessary async
    boundary from memory read metric emission.
    
    ## Testing
    
    - `just test -p codex-shell-command`
    - `cargo check -p codex-core`
  • [codex] simplify shell snapshot ownership (#27756)
    ## Why
    
    Shell snapshot lifecycle state was split between `Shell` and
    `SessionServices`: `Shell` carried the receiver while session code
    exposed and forwarded the raw sender. That coupled shell identity to
    mutable snapshot state and made refresh, inheritance, and file lifetime
    harder to reason about.
    
    ## What changed
    
    - make each `Arc<ShellSnapshot>` represent one cwd-specific snapshot
    generation
    - store the active generation in `SessionServices` with `ArcSwapOption`
    - have construction start the background build and expose only a
    cwd-validated snapshot path
    - use `ShellSnapshotFile` ownership to delete snapshot files
    automatically
    - pass snapshot paths explicitly to shell runtimes instead of storing
    snapshot state on `Shell`
    - preserve inherited and in-flight generations by pinning their `Arc`
    while they are in use
    
    ## Test plan
    
    - `cargo check -p codex-core --lib`
    - `just test -p codex-core 'shell_snapshot::tests'`
    - `just test -p codex-core
    shell_command_snapshot_still_intercepts_apply_patch`
    - `just test -p codex-core
    shell_snapshot_deleted_after_shutdown_with_skills`
  • skills: hide orchestrator skills with a local executor (#28333)
    ## Why
    
    App-server threads without a local executor need orchestrator-owned
    skills from the hosted `codex_apps` MCP server. Threads with the local
    executor already discover installed skills from the local filesystem.
    
    After the orchestrator skill provider was enabled for every app-server
    thread, local-executor threads also received the hosted skill catalog
    and the `skills.list` and `skills.read` tools. This changed the existing
    local behavior and could expose a second hosted copy of a skill that was
    already installed locally.
    
    ## What changed
    
    - Expose the thread's selected execution environments to extensions at
    thread startup.
    - Enable orchestrator skills only when the reserved local environment is
    not selected.
    - Apply that decision consistently to hosted skill catalog discovery,
    explicit skill injection, and the `skills.list` and `skills.read` tools.
    
    ## Verification
    
    - The existing no-executor app-server test continues to verify hosted
    skill discovery, invocation, and child-resource reads.
    - A new app-server test verifies that local-executor threads do not
    receive hosted skill context or `skills.*` tools.
  • Represent dynamic tools with explicit namespaces internally (#27365)
    Follow-up to #27356.
    
    ## Stack note
    
    This PR changes Codex's internal dynamic-tool shape while leaving
    `thread/start` unchanged. App-server therefore converts the existing
    per-tool input into explicit functions and namespaces before passing it
    to core.
    
    [#27371](https://github.com/openai/codex/pull/27371) updates
    `thread/start` to use the same explicit shape and removes this temporary
    conversion.
    
    ## Why
    
    Dynamic tools repeat namespace metadata on every function. Core should
    keep one explicit namespace with its member tools so descriptions and
    membership stay consistent across sessions and runtime planning.
    
    ## What changed
    
    - Represent dynamic tools as top-level functions or explicit namespaces
    in protocol and session state.
    - Read old flat rollout metadata and write the canonical hierarchy.
    - Flatten namespace members only when registering callable tools.
    - Keep `thread/start.dynamicTools` flat for now and normalize it at the
    app-server boundary.
    
    New builds can read old rollout metadata. Older builds cannot read newly
    written hierarchical metadata.
    
    ## Test plan
    
    - `just test -p codex-app-server
    thread_start_normalizes_legacy_dynamic_tools_into_model_request`
    - `just test -p codex-protocol
    session_meta_normalizes_legacy_dynamic_tools`
    - `just test -p codex-core
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
    - `just test -p codex-core
    tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call`
    - `just test -p codex-core code_mode_can_call_hidden_dynamic_tools`
    - `just test -p codex-tools`
  • [codex] update multi-agent v2 prompts (#28283)
    ## Summary
    
    - align the default multi-agent v2 root and subagent hints with the
    evaluated prompt guidance for direct collaboration-tool calls, parallel
    delegation, and shared workspaces
    - keep the current `interrupt_agent` tool name and existing
    concurrency-hint placement, with the explicit no-spawn instruction last
    - document the context tradeoff between `fork_turns="none"` and
    `fork_turns="all"` in the v2 `spawn_agent` description
    - extend the focused prompt and tool-surface tests
    
    ## Why
    
    The evaluated multi-agent prompt includes operational guidance that is
    missing from the current Codex defaults. This applies that guidance to
    the current tool surface without restoring stale `close_agent` or
    duplicated concurrency wording.
    
    ## User impact
    
    Multi-agent v2 receives clearer instructions about when and how to
    parallelize work, how agent workspaces interact, and how `fork_turns`
    affects subagent context. The existing default opt-out behavior remains
    in place.
    
    ## Testing
    
    - `just fmt`
    - `just test -p codex-core
    multi_agent_v2_default_usage_hints_use_configured_thread_cap`
    - `just test -p codex-core
    multi_agent_feature_selects_one_agent_tool_family`
  • Add selected-plugin precedence and attribution to the MCP catalog (#27884)
    ## Why
    
    **In short:** this PR resolves already-discovered MCP registrations. It
    does not read selected plugins or discover their MCP servers.
    
    The resolved MCP catalog currently builds config and auto-discovered
    plugin registrations before runtime contributors are applied. A
    thread-selected plugin needs a distinct precedence tier in that same
    initial resolution pass: otherwise a disabled lower-precedence winner
    can leave stale name-level state behind, and the winning MCP tools
    cannot be attributed to the selected package reliably.
    
    This PR adds that catalog boundary before executor discovery is
    connected.
    
    ## What changed
    
    - Added an explicit selected-plugin registration tier between
    auto-discovered plugins and explicit config.
    - Collected selected-plugin contributions before the initial catalog
    build, while leaving compatibility and generic extension overlays in
    their existing runtime phase.
    - Retained the winning plugin ID and display name directly on
    plugin-owned catalog registrations.
    - Derived MCP tool provenance from the winning catalog entry instead of
    joining against local-only plugin summaries.
    - Retained the winning selected server's tool approval policy in the
    running connection manager, so a selected registration cannot inherit
    approval behavior from a losing local plugin.
    - Kept remembered approval session-scoped for selected plugins until
    there is an authority-aware persistence contract; Codex will not write
    approval back to an unrelated local plugin.
    - Preserved existing name-level disabled vetoes for discovered plugins
    and config, while keeping a selected package's own disabled registration
    scoped to that registration.
    - Preserved deterministic selection order and existing config,
    compatibility, and extension precedence.
    
    The resulting order is:
    
    ```text
    auto-discovered plugin
      < selected plugin
      < explicit config
      < compatibility registration
      < extension overlay
    ```
    
    ## Behavior and scope
    
    This is a catalog and provenance change only. No production host
    contributes selected-plugin MCP registrations yet, so existing local MCP
    behavior remains unchanged.
    
    The stacked follow-up, #27870, installs the executor plugin provider
    that produces these registrations. App-server activation remains a
    separate final step.
    
    ## Verification
    
    Focused tests cover precedence, deterministic selected-plugin conflicts,
    disabled-veto behavior across catalog phases, managed requirements
    before selected-plugin resolution, winning-server approval policy, and
    attribution when local and selected packages share an ID or server name.
    CI owns execution of the test suite.
  • feat(app-server): filter threads by parent (#26662)
    ## Why
    
    Clients that display or coordinate spawned subagents need an
    authoritative snapshot of a thread's immediate spawned children when
    they connect to app-server or recover after missing live events.
    `thread/list` cannot query by parent, so clients must otherwise scan
    unrelated threads or reconstruct relationships from rollout history and
    transient events.
    
    The direct spawn relationship already exists in persisted
    `thread_spawn_edges` state. Review and Guardian threads do not
    participate in that lifecycle and are intentionally outside this
    filter's scope.
    
    ## What changed
    
    This adds an experimental `parentThreadId` filter to `thread/list`.
    Parent-filtered requests return direct spawned children from persisted
    state while preserving the existing response shape, explicit filters,
    sorting, and timestamp-only cursor behavior. The lookup does not read
    rollout transcripts or recursively return descendants.
    
    Supersedes #25112 with the narrower `thread/list` filter approach.
    
    ## How it works
    
    1. An experimental client passes a valid thread ID as `parentThreadId`.
    2. App-server routes the list through the existing thread-store and
    state-database boundaries.
    3. SQLite selects threads whose IDs have a direct persisted spawn edge
    from that parent.
    4. Omitted provider and source filters include all values; explicit
    filters keep ordinary `thread/list` semantics.
    5. Grandchildren, Review threads, and Guardian threads are excluded.
    
    ## Verification
    
    State (144 tests), rollout (69 tests), and focused app-server
    thread-list (31 tests) suites passed. Scoped Clippy checks and
    repository formatting also passed. Coverage includes direct spawned
    children, omitted grandchildren, pagination, malformed IDs, mixed source
    kinds, explicit filters, and operation without rollout files.
  • [codex] exec-server honors remote environment cwd and shell (#28122)
    ## Why
    
    Next slice needed to make progress on the `remote_env_windows` test is
    to support passing a Windows cwd for the remote environment and using
    that environment's native shell. This lets the test run a real Windows
    process instead of only recording an early path or shell mismatch.
    
    ## What
    
    - change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
    `PathUri`
    - convert local cwd values to URIs when constructing selections
    - preserve a remote primary cwd instead of replacing it with the local
    legacy fallback
    - prefer the selected environment's discovered shell for unified exec,
    falling back to the session shell when unavailable
    - convert back to a host-native absolute path at current native-only
    consumer boundaries
    - reject or deny unsupported foreign cwd values at the existing
    request-permissions boundary, with TODOs for its future migration
    - extend the hermetic Wine test to execute Windows PowerShell in
    `C:\windows` and verify successful process completion
    - record the current app-server rejection against the same Wine-backed
    remote Windows fixture when its cwd is supplied as a native Windows path
  • [codex] Carry exec-server cwd as PathUri (#28032)
    ## Why
    
    This is the second-to-last place in the exec-server protocol that needs
    to migrate to URIs to support cross-OS operation.
    
    ## What
    
    - Change `ExecParams.cwd` to `PathUri`.
    - Keep the cwd URI-shaped through core and rmcp producers, converting it
    to `AbsolutePathBuf` only in `LocalProcess::start_process`.
    - Reject non-native cwd URIs before launch and update the affected
    protocol documentation and call sites.
  • [codex] Send turn state through compact requests (#28002)
    ## Context
    
    Inline compaction is part of the active logical turn. Compact requests
    and the sampling requests around them should use the same turn state,
    including when compaction is the first request to establish it.
    
    ## Change
    
    Pass the turn-scoped `OnceLock` directly to inline v1 compaction so
    `/responses/compact` includes an established value in the existing HTTP
    header. Capture `x-codex-turn-state` from the compact response into that
    same lock, allowing pre-turn compact to establish the value that
    subsequent sampling reuses.
    
    V2 compact already uses the normal Responses HTTP/WebSocket path and
    continues to share the same `OnceLock` without separate plumbing. The
    first returned value wins for the logical turn.
    
    ## Test plan
    
    Integration coverage verifies that:
    
    - pre-turn v1 compact can establish state for the first sampling request
    - inline v1 compact receives established state over HTTP
    - inline v2 compact reuses established state over HTTP
    - inline v2 compact reuses established state over WebSocket
    
    CI validates the full change.
  • [codex] Send request-scoped turn state over WebSocket (#27996)
    ## Context
    
    Turn state is scoped to one logical turn, but the WebSocket path
    currently exchanges it through upgrade headers, which are scoped to the
    physical connection. A connection may be reused across turns, so its
    handshake cannot represent the turn lifecycle reliably.
    
    ## Change
    
    Exchange turn state on each WebSocket response request instead:
    
    - send an established value in `response.create.client_metadata`
    - read the returned value from the existing `response.metadata` event
    - retain the first value in the turn-scoped `ModelClientSession`
    `OnceLock`
    - start the next logical turn without state, even when it reuses the
    same WebSocket connection
    
    This gives WebSocket requests the same first-value-wins contract as the
    existing HTTP path.
    
    ## Test plan
    
    Integration coverage verifies that:
    
    - WebSocket replays returned state on same-turn follow-ups
    - later response metadata does not replace the first value
    - state resets at the logical turn boundary without requiring a
    reconnect
    
    CI validates the full change.
    
    ## Stack
    
    This is 1/2. #28002 builds on this request-scoped transport to carry
    established state through compact requests.
  • feat(app-server): enforce managed remote control disable (#27961)
    ## Why
    
    Managed deployments need a reliable deny gate for remote control.
    Persisted enablement and explicit startup requests currently remain able
    to start the transport, while the removed `features.remote_control` key
    is intentionally only a compatibility no-op.
    
    This adds a dedicated requirement that administrators can use to force
    remote control off without deleting the user's persisted preference.
    Removing the requirement and restarting restores the prior choice.
    
    ## What Changed
    
    - Added top-level `allow_remote_control` requirements parsing, sourced
    layer precedence, debug output, and `configRequirements/read` exposure
    as `allowRemoteControl`.
    - Added a typed transport policy captured from the startup requirements
    snapshot. Managed disable forces the initial state to disabled and
    prevents enrollment, refresh, connection, and persisted-preference
    mutation.
    - Rejected every `remoteControl/*` RPC before parameter deserialization
    with JSON-RPC `-32600` and `remote control is disabled by managed
    requirements`.
    - Preserved the existing disabled status notification and the previous
    behavior when the requirement is `true` or omitted.
    - Regenerated app-server protocol schemas and documented the new
    requirement.
    
    ## Verification
    
    - Confirmed all remote-control RPCs, including a malformed request,
    return the managed-policy error while the initial status notification
    remains `disabled`.
    - Confirmed explicit ephemeral startup and persisted enablement make no
    backend connection and leave the SQLite preference unchanged.
    - Confirmed `allow_remote_control = true` does not enable or block
    remote control and `configRequirements/read` returns
    `allowRemoteControl: false` for the deny policy.
    
    Related issue: N/A (managed-policy hardening).
  • [codex] Gate plugin MCP servers by auth route (#27459)
    ## Context
    
    Some plugins expose both Apps and MCP servers. This PR moves auth-aware
    surface projection into `core-plugins::PluginsManager`, so callers get a
    consistent effective plugin view. Later PRs narrow the conflict rule and
    update listing/install paths.
    
    The high level goal of this PR is to set up the plumbing to
    conditionally filter App/MCP in the plugin manager layer. We start by
    removing MCP servers when using SIWC/Codex-backend auth, and removing
    Apps when using API-key-style auth.
    
    This PR is now stacked on #27652, which contains only the constructor
    plumbing for seeding `PluginsManager` with the current auth mode.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - API-key/non-ChatGPT routes hide plugin Apps and keep plugin MCPs.
    - ChatGPT/SIWC with Apps enabled keeps plugin Apps and suppresses MCPs
    for dual-surface plugins.
    - MCP-only plugins stay available for ChatGPT/SIWC sessions.
    - Cached plugin load outcomes are re-projected when auth mode changes.
    
    ## Validation
    
    ```bash
    cargo test -p codex-core-plugins plugin_auth_projection
    cargo test -p codex-core list_tool_suggest_discoverable_plugins
    git diff --check
    ```
  • [codex] Add auth mode to plugin manager constructor (#27652)
    ## Context
    
    Plugins can expose more than one way for Codex to use them: App
    connectors for ChatGPT/SIWC-backed sessions and MCP servers for API key
    login sessions. The broader goal is to make `PluginsManager` the place
    that understands which plugin surfaces should be visible for the current
    auth route, so callers do not each have to make that decision
    themselves.
    
    This PR is the small setup step for that work. It lets the plugin
    manager be created with the current `AuthMode`, which gives the followup
    auth routing PRs the information they need without relying on setter
    injection.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Let `PluginsManager::new_with_restriction_product` accept an initial
    `AuthMode`.
    - Keep `PluginsManager::new` behavior unchanged for ordinary callers.
    
    ## Validation
    
    ```bash
    cargo test -p codex-core-plugins plugins_manager_tracks_auth_mode
    cargo test -p codex-core list_tool_suggest_discoverable_plugins
    git diff --check
    ```
    
    ---------
    
    Co-authored-by: Xin Lin <xl@openai.com>
  • [codex] Limit app-based plugin suggestions to remote catalogs (#27988)
    ## Summary
    
    - Keep local plugin suggestions bounded to fallback and explicitly
    configured plugins.
    - Preserve app-overlap recommendations for remote plugins using cached
    catalog metadata.
    - Remove the WSL-specific local discovery exception and move
    manager-owned discovery tests into `codex-core-plugins`.
    
    ## Why
    
    Local curated marketplaces were allowlisted before plugin detail
    loading, so every uninstalled candidate could be deep-read before its
    app IDs were checked. That caused per-turn reads of candidate plugin
    manifests, skills, app configs, hooks, and MCP configs, which is
    especially expensive on slow disks.
    
    Remote discovery does not need those local candidate reads because app
    IDs are already available in the cached remote catalog. Installed local
    plugins are still loaded when needed to determine the user's installed
    app IDs.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core-plugins discoverable::tests` (13 passed)
    - `just test -p codex-core plugins::discoverable::tests` (4 passed)
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `git diff --check`
  • [codex] add latency tracing spans (#27710)
    ## Why
    
    We have some large gaps in our thread start, resume, and pre-sampling
    traces that make it hard to tell where latency is coming from.
    
    ## What Changed
    
    - Added coarse spans around thread start/resume, turn context
    construction, rollout reconstruction, skill/plugin loading, and tool
    preparation.
    - Added a breakdown of discoverable-tool preparation across connector
    loading, plugin discovery, and local plugin details.
    
    ## Testing
    
    - `cargo check -p codex-app-server -p codex-core -p codex-core-skills -p
    codex-core-plugins`
    - Built the app-server locally and exercised thread start, first turn,
    follow-up turn, server restart, thread resume, and a resumed turn.
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • Add Guardian catalog diagnostics metadata (#27109)
    ## Why
    
    We need request-level evidence for Guardian cases where
    `codex-auto-review` is missing from the client-side model catalog and
    the review falls back to the parent model.
    
    ## What changed
    
    - Add `guardian_catalog_contains_auto_review` to Guardian Responses API
    client metadata.
    - Add `guardian_model_provider_id` to Guardian Responses API client
    metadata.
    - Keep review-session metadata optional so callers without metadata
    preserve the existing `None` path.
    - Add tests for override, normal preferred-model, and
    missing-auto-review-catalog behavior.
    
    ## Validation
    
    - `just test -p codex-core
    guardian_review_records_missing_auto_review_model_in_request_metadata`
    - `just test -p codex-core
    guardian_review_uses_model_catalog_override_when_preferred_review_model_exists`
    - `just test -p codex-core
    guardian_review_uses_preferred_review_model_without_model_catalog_override`
    - `git diff --check origin/main`
  • [codex] add roles to realtime append text (#27936)
    ## Summary
    
    Add an explicit `user` or `developer` role to
    `thread/realtime/appendText` and propagate it through the realtime input
    queue into `conversation.item.create`. Older JSON clients that omit the
    field continue to default to `user`.
    
    This lets app-provided context such as memory retain developer authority
    without bypassing app-server through a renderer-owned data channel. The
    app-server schemas, API documentation, and focused protocol and
    websocket coverage are updated with the new contract.
    
    The Codex Apps consumer is tracked in
    [openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
  • feat: use encrypted local secrets for MCP OAuth (#27541)
    ## Summary
    
    - store MCP OAuth credentials in the configured auth credential backend
    - support encrypted-local OAuth storage, including legacy keyring
    migration
    - propagate the credential backend through MCP refresh, session, CLI,
    and app-server paths
    
    ## Stack
    
    1. #27504 — config and feature flag
    2. #27535 — auth-specific secret namespaces
    3. #27539 — encrypted CLI auth storage
    4. this PR — encrypted MCP OAuth storage
    
    This is a parallel review stack; the original #17931 remains unchanged.
    
    ## Tests
    
    - `just test -p codex-rmcp-client` (the transport round-trip test passed
    after building the required `codex` binary and retrying)
    - `just test -p codex-mcp`
    - `just test -p codex-app-server
    refresh_config_uses_latest_auth_keyring_backend`
    - `just test -p codex-core
    refresh_mcp_servers_is_deferred_until_next_turn`
    - `just test -p codex-cli mcp`
    - `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
    -p codex-app-server -p codex-protocol`
    - `just bazel-lock-check`
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • Support plaintext agent messages (#27830)
    ## Why
    
    Multi-agent v2 `send_message` deliveries already reach the receiving
    model as typed `agent_message` items with encrypted content.
    Child-completion notifications are generated by Codex itself, so their
    content is plaintext and previously fell back to a serialized JSON
    envelope inside an assistant message.
    
    With plaintext `input_text` supported for `agent_message`, both delivery
    paths can use the same model-visible type while preserving explicit
    author and recipient metadata.
    
    ## What changed
    
    - add plaintext `input_text` support to `AgentMessageInputContent` and
    regenerate the affected app-server schemas
    - preserve `InterAgentCommunication` as structured mailbox input instead
    of converting it to assistant text
    - record delivered communications as typed `agent_message` history items
    - persist a dedicated rollout item so local delivery metadata such as
    `trigger_turn` remains available without leaking into the Responses
    request
    - reconstruct typed agent messages on resume and preserve fork-turn
    truncation behavior
    - remove request-time assistant-content parsing
    - preserve plaintext and encrypted inter-agent deliveries in stage-one
    memory inputs
    - normalize and link plaintext and encrypted agent messages in rollout
    traces without treating inbound messages as child results
    - cover the real MultiAgent V2 child-completion path end to end with
    deterministic mailbox synchronization
    
    ## Verification
    
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core input_queue_drains_mailbox_in_delivery_order
    record_initial_history_reconstructs_typed_inter_agent_message
    fork_turn_positions_use_inter_agent_delivery_metadata`
    - `just test -p codex-memories-write
    serializes_inter_agent_communications_for_memory`
    - `just test -p codex-rollout-trace
    agent_messages_preserve_routing_and_content
    sub_agent_started_activity_creates_spawn_edge`
    - `just test -p codex-rollout-trace
    agent_result_edge_falls_back_to_child_thread_without_result_message`
    - `just test -p codex-protocol -p codex-rollout -p
    codex-app-server-protocol`
  • fix(plugins) rm plugin descriptions (#23254)
    ## Summary
    Removes Plugin descriptions from the dev message, since descriptions of
    skills and MCPs cover the capabilities offered by the plugin.
    
    ## Testing
    - [x] Updates unit tests
  • feat: add secret auth storage configuration (#27504)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. The encrypted local secrets backend avoids storing large
    serialized auth payloads directly in the OS keyring, but selecting that
    backend needs an independently reviewable feature/config layer before
    the auth and secrets implementation is wired in.
    
    ## What Changed
    
    - Added the stable `secret_auth_storage` feature, enabled by default on
    Windows and disabled by default elsewhere.
    - Added `AuthKeyringBackendKind` and config resolution for full and
    bootstrap config loading.
    - Applied managed feature requirements when resolving the bootstrap auth
    backend.
    - Updated the generated config schema and added focused tests.
    
    This is the base PR for #17931. The auth, secrets, MCP, CLI, TUI, and
    app-server implementation remains in that follow-up PR.
    
    ## Validation
    
    - `just test -p codex-features`
    - `just test -p codex-config`
    - `just test -p codex-core
    resolve_bootstrap_auth_keyring_backend_kind_uses_secret_auth_storage_feature`
    - `just write-config-schema`
    - `just fix -p codex-core`
    
    The full `just test -p codex-core` run compiled successfully and ran
    2,690 tests; 2,589 passed, one was flaky, and 101 environment-sensitive
    tests failed because this shell injects a `pyenv` rehash warning into
    command output or because sandboxed subprocesses timed out.
  • Handle standalone image generation failures as terminal items (#27920)
    ## Why
    
    Standalone image generation emitted a started item but no terminal item
    when the backend failed. Clients could leave the operation unresolved or
    render it as successful.
    
    ## What changed
    
    - Emit a terminal image-generation item with `status: "failed"` when
    generation or editing fails.
    - Skip image persistence for failed terminal items.
    - Render failed image generation distinctly in TUI history.
    - Preserve the status when handling live and replayed terminal items.
    
    ## Looks for TUI, App-Side change needed 
    
    <img width="867" height="89" alt="image"
    src="https://github.com/user-attachments/assets/9e32342f-a982-411e-8498-456639fc468a"
    />
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - App-server image-generation tests
    - Core stream-event tests
    - TUI image-generation lifecycle and snapshot tests
    - Scoped Clippy and formatting
  • sandboxing: migrate cwd inputs to PathUri (#27816)
    ## Why
    
    Sandbox cwd values can cross app-server and exec-server host boundaries.
    They should retain URI semantics until the receiving host validates them
    instead of being interpreted early as native paths.
    
    ## What
    
    - Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
    and transform inputs.
    - Convert command and policy cwd once in `SandboxManager::transform`,
    then keep launch requests native.
    - Preserve sandbox cwd over remote filesystem transport and reject
    non-native URIs without fallback.
    - Cache paired native/URI turn-environment cwd values during migration,
    with immutable access to keep them synchronized.
    - Extend existing protocol, forwarding, transform, and core runtime
    tests.
  • chore: prompt MAv2 (#27919)
    Prompt update of MAv2
  • realtime: add AVAS architecture override (#27720)
    ## Summary
    
    Adds a `RealtimeConversationArchitecture` option for realtime
    conversation startup, with `realtimeapi` as the default and `avas` as an
    opt-in architecture.
    
    The AVAS path is limited to realtime v1 conversational WebRTC starts,
    and WebRTC call creation appends `intent=quicksilver&architecture=avas`
    to `/v1/realtime/calls`. The existing sideband websocket still joins by
    `call_id`.
    
    This also exposes the per-session architecture override through
    app-server v2 `thread/realtime/start` params and updates the config
    schema for `[realtime].architecture`.
    
    ## Validation
    
    - `just fmt`
    - `just write-config-schema`
    - `just test -p codex-api sends_avas_session_call_query_params`
    - `just test -p codex-core -E
    'test(~conversation_webrtc_start_uses_avas_architecture_query)'`
    - `just test -p codex-core -E 'test(realtime_loads_from_config_toml)'`
    - `just test -p codex-app-server-protocol -E
    'test(~serialize_thread_realtime_start) |
    test(generated_ts_optional_nullable_fields_only_in_params)'`
    - `just test -p codex-app-server -E
    'test(realtime_webrtc_start_emits_sdp_notification)'`
  • [ez][codex-rs] Support approvals reviewer in app defaults (#27075)
    [from codex]
    
    ## Summary
    
    - add `approvals_reviewer` support to `[apps._default]`
    - resolve connected-app reviewers in per-app, app-default, then global
    order
    - expose the setting through the v2 config API and regenerate schema
    fixtures
    
    ## Context
    
    PR #25167 added `apps.<connector_id>.approvals_reviewer`, but the shared
    app defaults table could not specify the reviewer. This extends the same
    behavior to `[apps._default]` while preserving per-app overrides.
    
    Managed `allowed_approvals_reviewers` requirements still constrain both
    default and per-app values. A disallowed app value falls back to the
    global reviewer, and non-app MCP servers continue using the global
    reviewer.
    
    ## Testing
    
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just fmt`
    - `just test -p codex-config`
    - `just test -p codex-core app_approvals_reviewer`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server config_read_includes_apps`
  • Make MCP server contributions thread-scoped (#27670)
    ## Why
    
    `selectedCapabilityRoots` belongs to one thread, but MCP contributors
    previously received only the global Codex config. That left no clean way
    for a selected executor capability to contribute MCP servers to its own
    thread.
    
    ## What this PR does
    
    - Gives MCP contributors a small context containing the config and, for
    a running thread, its frozen host-seeded inputs.
    - Uses the same thread inputs during startup, status queries, refreshes,
    and skill dependency checks.
    - Keeps threadless MCP operations and the existing hosted Apps behavior
    unchanged.
    - Adds coverage showing that two threads resolve independent
    registrations and that later lifecycle mutations do not change the
    frozen MCP inputs.
    
    This PR does not discover plugin manifests, add MCP servers, or launch
    anything new. It only establishes the thread-scoped registration
    boundary.
    
    ## Follow-ups
    
    - Resolve selected executor plugin roots through their owning
    environment filesystem.
    - Convert their stdio MCP declarations into environment-bound
    registrations and add an executor MCP end-to-end test.
    
    ## Verification
    
    - `just fmt`
    - `cargo check --tests -p codex-protocol -p codex-extension-api -p
    codex-mcp-extension -p codex-core -p codex-app-server`
    
    Tests and Clippy were not run.
  • [codex] Load AGENTS.md from all bound environments (#27696)
    ## Why
    
    We already have the machinery to support multiple environments on a
    single thread, but we only show the model the contents of `AGENTS.md`
    files in the primary environment.
    
    We should show the model all of the relevant project instructions when
    we know there's more than one environment.
    
    ## Known Gaps
    
    As discussed in the RFC, this implementation:
    
    1. doesn't handle environments being added/removed to/from the thread
    after its creation
    2. it doesn't enforce an aggregate context budget across environments,
    and instead applies the configured project maximum independently to each
    environment
    
    ## Implementation
    
    - Discover project instructions in environment order with an independent
    byte budget per environment and preserve source provenance/order.
    - Keep the legacy fragment byte-for-byte when exactly one environment
    contributes project instructions; use environment-labeled sections when
    two or more environments contribute.
    - Freeze the complete rendered fragment in `LoadedAgentsMd`, insert it
    directly into requests, and recognize both layouts in contextual and
    memory filtering.
    - Add exact rendering, independent-budget, source-order,
    creation-snapshot, and consumer coverage without changing app-server
    schemas.
  • Keep request_user_input direct-model only (#27316)
    ## Why
    
    `request_user_input` has direct blocking semantics when invoked by the
    model. When it is exposed as a nested code-mode tool, the call has to
    flow through code-mode waiting and continuation behavior instead, which
    is not the behavior we want for this user-input request surface.
    
    ## What changed
    
    - Mark `request_user_input` with `ToolExposure::DirectModelOnly` when
    registering the core utility tool.
    - Keep `request_user_input` direct-model visible, including in
    code-mode-only planning.
    - Add focused `spec_plan_tests` coverage that verifies
    `request_user_input` remains visible and registered as
    direct-model-only, while it is omitted from the nested code-mode tool
    description.
    
    No active goal suppression or runtime unavailability behavior is
    included in this PR.
    
    ## Validation
    
    - No new build/test run for this housekeeping pass, per maintainer
    request.
    - Earlier targeted run, confirmed from session context: `just test -p
    codex-core request_user_input` passed.
  • Add request_user_input auto-resolution window contract (#27256)
    ## Why
    
    `request_user_input` is moving beyond its original plan-mode-only
    workflow, and future default/goal-mode usage needs a way for the model
    to ask helpful but non-blocking questions without forcing the turn to
    wait forever. This PR adds an explicit `autoResolutionMs` contract so a
    later client/runtime change can auto-resolve unanswered prompts after a
    bounded window while leaving truly blocking questions unchanged.
    
    This is contract plumbing only; it does not implement the client-side
    timer or auto-selection behavior, and the model-facing description
    treats the field as reserved unless the current runtime explicitly
    supports auto-resolution.
    
    ## What Changed
    
    - Added optional `autoResolutionMs` to the model-facing
    `request_user_input` args and core `RequestUserInputEvent`.
    - Added model-facing schema text for `autoResolutionMs` while marking it
    reserved for runtimes that explicitly support auto-resolution.
    - Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
    normalization by clamping out-of-range model-provided values.
    - Propagated the field through app-server v2
    `ToolRequestUserInputParams`, app-server request forwarding, generated
    TypeScript, and JSON schema fixtures.
    - Updated app-server, core, protocol, and TUI call sites/tests so
    omitted values preserve existing `None`/`null` behavior and coverage
    verifies a `Some(60_000)` round trip.
    
    ## Verification
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core request_user_input`
    - `just test -p codex-app-server request_user_input_round_trip`
    - `just test -p codex-tui request_user_input`
    - `just test -p codex-protocol`
  • [codex] resolve environment shell metadata eagerly (#27709)
    ## Why
    
    Turn construction passed resolved environments through several layers
    while leaving the environment shell unresolved. As a result,
    model-visible environment context could fall back to the session shell
    instead of reporting the selected remote environment's shell.
    
    Resolve environment metadata at the turn-context boundary so each turn
    carries the shell that belongs to its selected environment. Keep request
    validation in app-server, where invalid selections can be returned as
    straightforward JSON-RPC errors without coupling core turn construction
    to that policy.
    
    ## What changed
    
    - resolve environment selections eagerly in
    `new_turn_context_from_configuration`
    - store the full resolved `Shell` on each `TurnEnvironment`
    - simplify the now-redundant resolved-environment constructor plumbing
    - keep duplicate and unknown-environment validation as a small
    app-server preflight
    - add a remote-environment integration test that runs a full
    `test_codex` turn and verifies the model-visible environment message
    reports `bash`
    
    ## Testing
    
    - `cargo check -p codex-core --test all -p codex-app-server`
    - `remote_test_env_exposes_bash_shell_to_model` on the Linux
    remote-executor harness
  • [codex] Remove async_trait from first-party code (#27475)
    ## Why
    
    First-party async traits should expose their `Send` contracts explicitly
    without requiring `async_trait`. This completes the migration pattern
    established in #27303 and #27304.
    
    ## What changed
    
    - Replaced the remaining first-party `async_trait` traits with native
    return-position `impl Future + Send` where statically dispatched and
    explicit boxed `Send` futures where object safety is required.
    - Kept implementations behavior-preserving, outlining existing async
    bodies into inherent methods where that keeps the diff reviewable.
    - Removed all direct first-party `async-trait` dependencies and the
    workspace dependency declaration.
    - Added a cargo-deny policy that permits `async-trait` only through the
    remaining transitive wrapper crates.
    - Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
    keep the full cargo-deny check passing.
    
    ## Validation
    
    - `just test -p codex-exec-server`: 216 passed, 2 skipped.
    - `just test -p codex-model-provider`: 39 passed.
    - `just test -p codex-core` and `just test`: changed tests passed;
    remaining failures are environment-sensitive suites unrelated to this
    migration.
    - `cargo deny check`
    - `just fix`
    - `just fmt`
    - `cargo shear`
    - `just bazel-lock-check`
  • Add spans to turn lifecycle gaps (#27623)
    ## Why
    Codex app-server latency traces do not granularly cover turn task
    startup and inter-request handoffs. These spans help attribute time
    across task execution, startup prewarm, in-flight tool completion, and
    rollout persistence.
    
    ## What changed
    - Add `session_task.run` spans around task execution and
    `session_task.flush_rollout` around flushing pending conversation
    transcript writes to durable storage
    - Add `regular_task.prepare_run_turn` around regular-turn startup (Send
    the `TurnStarted` event, reset turn-specific reasoning state, and
    resolve any startup prewarm)
    - Add `startup_prewarm.resolve` around waiting for background session
    prewarming to finish, fail, time out, or be cancelled
    - Add a function-level trace span around draining in-flight tool calls
    (Wait for tool calls to complete, record tool result in conversation
    history, and other bookkeeping)
    
    ## Verification
    Trigger Codex rollout and observe new spans are included
  • Route image extension reads through turn environments v2 (#27498)
    ## Why
    
    Image generation used `std::fs::read` for referenced image paths, which
    did not support environment-backed filesystems or their sandbox context.
    
    ## What changed
    
    - Expose optional turn environments to extension tool calls.
    - Include each environment’s ID, working directory, filesystem, and
    sandbox context.
    - Read referenced images through the selected environment filesystem.
    - Keep sandbox usage at the extension call site so extensions can choose
    the appropriate access mode.
    - Consolidate image request construction into one async function.
    - Add coverage for successful environment reads and read failures.
    
    ## Validation
    
    - `cargo check -p codex-image-generation-extension --tests`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    `just test -p codex-image-generation-extension` could not complete
    because the build exhausted available disk space.
  • [codex] Move persistence policy application into ThreadStore (#27318)
    Move the application of the persistence policy into the thread store, so
    thread stores can get raw append items rather than canonical append
    items. This will enable store-specific projections over the raw input
    items.
  • Include thread id in token budget context (#27663)
    ## Why
    
    The token budget full-context fragment identifies the current context
    window, but not the thread that owns that window. Including the thread
    id makes the initial context-window metadata self-contained, and
    `get_context_remaining` also needs to be usable from Code Mode without
    forcing callers to parse the model-facing fragment string.
    
    ## What changed
    
    - Include the session thread id in the initial `<token_budget>` context
    fragment.
    - Expose `get_context_remaining` as a Code Mode nested tool while
    keeping `new_context` direct-model-only.
    - Keep direct model-facing `get_context_remaining` output as the
    existing `<token_budget>` text fragment.
    - Return only `tokens_left` from the Code Mode structured result for
    `get_context_remaining`.
    - Update token-budget integration tests and add Code Mode coverage for
    the structured result.
    
    ## Verification
    
    - `just test -p codex-core token_budget`
    - `just test -p codex-core
    code_mode_get_context_remaining_returns_structured_result`
    - `just test -p core_test_support redacted_text_mode_normalizes_uuids`