Commit Graph

223 Commits

  • Resolve per-thread multi-agent runtime (#25722)
    Stack split from #25708. Original PR intentionally left open. This third
    PR resolves the effective per-thread multi-agent runtime from persisted
    metadata, inherited runtime, and current model selection.
  • Wire managed MITM CA trust into child env (#22668)
    ## Stack
    1. Parent PR: #18240 uses named MITM permissions config.
    2. This PR wires managed MITM CA trust into spawned child processes.
    
    ## Why
    When Codex terminates HTTPS for limited mode or MITM hooks, child HTTPS
    clients need to trust Codex's managed MITM CA. Exporting proxy URLs
    alone is not enough, but blindly replacing user CA settings would be
    wrong: it can break custom enterprise/test roots, leak unreadable CA
    files into generated bundles, or make the child env disagree with its
    sandbox policy.
    
    ## Summary
    1. Build immutable managed CA bundles under `$CODEX_HOME/proxy` that
    include native roots, the managed MITM CA, and only inherited or
    command-scoped CA bundles the child is allowed to read.
    2. Export curated CA env vars alongside managed proxy env vars while
    preserving user CA override semantics, including nested Codex
    `SSL_CERT_FILE` precedence.
    3. Thread generated CA bundle paths into child sandbox readable roots,
    including debug sandbox execution, so the exported env vars work inside
    sandboxed commands.
    4. Remove only Codex-generated MITM CA bundle env when a child
    intentionally drops managed proxying for escalation or no-proxy retry.
    5. Document the managed CA bundle behavior and cover env injection,
    per-child bundle generation, sandbox readable roots, and no-proxy
    cleanup in tests.
    
    ## Validation
    1. Ran `just test -p codex-network-proxy`.
    2. Ran `just test -p codex-protocol`.
    3. Ran `just fix -p codex-network-proxy -p codex-protocol`.
    4. Tried focused `codex-core` validation, but the crate currently fails
    to compile in `core/tests/suite/guardian_review.rs` because an existing
    `Op::UserInput` initializer is missing `additional_context`.
    
    ---------
    
    Co-authored-by: Eva Wong <evawong@openai.com>
  • [codex] Consolidate shared prompts in codex-prompts (#25151)
    ## Why
    
    `codex_core` is consistently a bottleneck for incremental builds during
    iteration. The simplest fix is to make the crate smaller.
    
    ## Summary
    
    `codex-core` owns several reusable prompt renderers and static prompt
    assets, which makes the crate harder to split apart.
    
    Rename `codex-review-prompts` to `codex-prompts` and move shared review,
    goal, permissions, compaction, realtime, hierarchical AGENTS.md, and
    `apply_patch` prompts into it. Move prompt-only tests and update
    consumers and `CODEOWNERS`.
    
    ## Validation
    
    - `just test -p codex-prompts -p codex-apply-patch`
    - `just test -p codex-core prompt_caching`
    - Bazel builds for the affected crates
  • windows-sandbox: pass workspace roots to runner (#24108)
    ## Why
    
    #23813 switches the Windows sandbox runner path to `PermissionProfile`,
    but it still left one runtime anchor for resolving symbolic
    `:workspace_roots` entries. That is not enough once a turn has multiple
    effective workspace roots: exact entries and deny globs under
    `:workspace_roots` need to be materialized for every runtime root before
    the command runner chooses token mode or builds ACL plans.
    
    ## What Changed
    
    - Replaces the Windows runner/setup `permission_profile_cwd` plumbing
    with `workspace_roots: Vec<AbsolutePathBuf>`.
    - Resolves Windows-local `PermissionProfile` data with
    `materialize_project_roots_with_workspace_roots(...)` instead of the
    single-cwd helper.
    - Threads `Config::effective_workspace_roots()` through core execution,
    unified exec, TUI setup/read-grant flows, app-server setup, app-server
    `command/exec`, and `debug sandbox` on Windows.
    - Preserves those workspace roots through the zsh-fork escalation
    executor instead of rebuilding them from `sandbox_policy_cwd`.
    - Makes `ExecRequest::new(...)` and the remaining
    `build_exec_request(...)` helper path take
    `windows_sandbox_workspace_roots` explicitly so new call sites cannot
    silently fall back to `vec![cwd]`.
    - Clarifies the `debug sandbox` non-Windows comment: remaining
    cwd-dependent resolution still uses `sandbox_policy_cwd`, while
    `:workspace_roots` entries are already materialized from config roots.
    - Updates elevated runner IPC `SpawnRequest` to send `workspace_roots`
    and bumps the framed IPC protocol version to `3` for the payload shape
    change.
    - Adds Windows-local resolver coverage for expanding exact and glob
    `:workspace_roots` entries across multiple roots, plus core helper
    coverage proving explicit roots are preserved.
    
    ## Verification
    
    - `cargo check -p codex-windows-sandbox -p codex-core -p codex-tui -p
    codex-cli -p codex-app-server`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-core unix_escalation`
    - `cargo test -p codex-app-server windows_sandbox`
    - `cargo test -p codex-tui windows_sandbox`
    - `cargo test -p codex-cli debug_sandbox`
    - `just test -p codex-core unified_exec`
    - `just test -p codex-core
    build_exec_request_preserves_windows_workspace_roots`
    - `env -u CODEX_NETWORK_PROXY_ACTIVE -u
    CODEX_NETWORK_ALLOW_LOCAL_BINDING just test -p codex-app-server --lib
    command_exec`
    - `just test -p codex-windows-sandbox`
    - `just test -p codex-exec sandbox`
    - `just fix -p codex-core -p codex-app-server -p codex-windows-sandbox`
    
    A local macOS cross-check with `cargo check --target
    x86_64-pc-windows-msvc ...` did not reach crate Rust code because native
    dependencies require Windows SDK headers (`windows.h` / `assert.h`) in
    this environment; Windows CI remains the real target validation.
    
    Two local targeted filters compile but do not run assertions on macOS:
    `env -u CODEX_NETWORK_PROXY_ACTIVE -u CODEX_NETWORK_ALLOW_LOCAL_BINDING
    just test -p codex-app-server --lib command_exec_processor` matched zero
    tests, and `just test -p codex-linux-sandbox landlock` matched zero
    tests because the landlock suite is Linux-only.
  • [codex] Add user input client ids (#24653)
    ## Summary
    
    Adds an optional `clientId` field to app-server v2 `UserInput` and
    carries it through the core `UserInput` model so clients can correlate
    echoed user input items without relying on payload equality.
    
    ## Details
    
    - Adds `client_id: Option<String>` to core `UserInput` variants.
    - Exposes the v2 app-server field as `clientId` on the wire and in
    generated TypeScript.
    - Preserves the id when converting between app-server v2 and core
    protocol types.
    - Regenerates app-server schema fixtures.
    
    ## Validation
    
    - `just fmt`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-protocol`
    - `git diff --check`
  • Wire task completion into thread-idle lifecycle (#24928)
    ## Why
    
    #24744 introduced the thread idle lifecycle hook so idle continuation
    can be owned by lifecycle contributors instead of hard-coded goal
    runtime plumbing. Task completion still called
    `goal_runtime_apply(GoalRuntimeEvent::MaybeContinueIfIdle)` directly, so
    the post-turn idle transition remained goal-specific and did not notify
    generic thread lifecycle contributors.
    
    ## What Changed
    
    - Add `Session::emit_thread_idle_lifecycle_if_idle()` to gate idle
    emission on both no active turn and no queued trigger-turn mailbox work.
    - Call that helper when a task clears the active turn, replacing the
    direct `GoalRuntimeEvent::MaybeContinueIfIdle` path.
    - Cover the behavior with `codex-core` session tests for emitting after
    task completion and suppressing idle emission while trigger-turn mailbox
    work is pending.
    
    ## Verification
    
    - New tests in `core/src/session/tests.rs` exercise the idle lifecycle
    emission and trigger-turn mailbox guard.
  • Add turn error lifecycle contributor (#24916)
    Summary
    - Add TurnErrorInput and TurnLifecycleContributor::on_turn_error to the
    extension API.
    - Emit the turn-error lifecycle from core turn error paths, including
    usage limit failures.
    - Add direct lifecycle coverage for the emitted error facts and stores.
    
    Tests
    - just fmt
    - git diff --check
    - Not run: full tests or clippy (per instructions)
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • feat: add manual and remote_v2 tags to compaction metric (#24608)
    ## Why
    `codex.task.compact` only distinguished `local` vs `remote`, which made
    it hard to answer simple counter questions in Statsig. Manual `/compact`
    and automatic compaction were collapsed together, and the legacy remote
    path was also collapsed with `remote_compaction_v2`.
    
    ## What Changed
    - route `codex.task.compact` through a shared helper in
    `core/src/tasks/mod.rs`
    - add a `manual=true|false` tag so manual and automatic compaction can
    be counted separately
    - split the remote tag into `remote` and `remote_v2`
    - emit the metric from the inline auto-compaction path in
    `core/src/session/turn.rs` as well as the manual `CompactTask` path in
    `core/src/tasks/compact.rs`
    - add focused unit coverage for the new tag shapes in
    `core/src/tasks/mod_tests.rs`
    
    ## Verification
    - added unit coverage in `core/src/tasks/mod_tests.rs` covering manual
    `remote_v2` tags and automatic `local` tags
  • [codex] Make active turn task singular (#24105)
    ## Why
    
    `ActiveTurn` already runs at most one task: starting a task requires
    that no task is present, and replacement aborts existing work first.
    Representing that state as an `IndexMap` leaves a multi-task shape for a
    single-task invariant and makes each lifecycle lookup operate like a
    collection lookup.
    
    The slot remains optional because goal continuation uses an empty active
    turn as a reservation while deciding whether to start continuation work.
    
    ## What changed
    
    - Replace `ActiveTurn.tasks` with `task: Option<RunningTask>`.
    - Update task abort/completion, session lookup and steering, input-queue
    matching, goal reservation, and network-approval lookup to operate on
    the singular slot.
    - Mutate the singular task slot directly instead of retaining
    collection-era add/remove/take helpers.
    - Record token usage on the completing active task span without a
    regular-task-only opt-in flag.
    
    ## Validation
    
    - `cargo test -p codex-core --lib session::tests::steer_input`
    - `cargo test -p codex-core --lib
    session::tests::abort_empty_active_turn_preserves_pending_input`
    - `cargo test -p codex-core --lib
    session::tests::queued_response_items_for_next_turn_move_into_next_active_turn`
    - `cargo test -p codex-core --lib
    session::tests::active_goal_continuation_runs_again_after_no_tool_turn`
    - `cargo test -p codex-core --lib
    session::tests::abort_regular_task_emits_turn_aborted_only`
    - `cargo test -p codex-core --lib session::input_queue::tests`
  • [codex] Use TurnInput for session task input (#24151)
    ## Why
    
    The idea here is to erase the difference between initial and followup
    inputs to a turn. Followup inputs are already represented as TurnInput.
    
    Eventual goal is not to have explicit on task input at all and pull
    everything from input Q.
    
    ## What Changed
    
    - Changes `SessionTask::run` and the erased `AnySessionTask::run` path
    to accept `Vec<TurnInput>`.
    - Wraps user-submitted spawn input as `TurnInput::UserInput` at the
    session task start boundary.
    - Updates `run_turn` to record initial `TurnInput` using the same hook
    and recording path used for pending input.
    - Keeps review-specific conversion local to `ReviewTask`, where the
    sub-Codex one-shot API still expects `Vec<UserInput>`.
    - Moves the synthetic compact prompt into `CompactTask` and starts
    compact tasks with empty task input.
    
    ## Validation
    
    - `cargo check -p codex-core`
    - `just test -p codex-core -E
    'test(task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input)
    | test(queued_response_items_for_next_turn_move_into_next_active_turn) |
    test(steered_input_reopens_mailbox_delivery_for_current_turn)'`
  • Add trace_id to TurnStartedEvent (#23980)
    ## Why
    [Recent PR](https://github.com/openai/codex/pull/22709) removed
    `trace_id` from `TurnContextItem`.
    
    ## What changed
    - Add to `TurnStartedEvent` so rollout consumers can correlate turns
    with telemetry traces.
    - Note that the branch name is out of date because I originally re-added
    to `TurnContextItem`, but we decided to move it to `TurnStartedEvent`.
    
    ## Verification
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core --lib
    regular_turn_emits_turn_started_without_waiting_for_startup_prewarm`
    - `cargo test -p codex-core --test all
    emits_warning_when_resumed_model_differs`
    - `cargo test -p codex-rollout`
    - `cargo test -p codex-state`
  • core: refresh active permission profiles at runtime (#22931)
    ## Why
    
    Once a named permission profile is selected, runtime state has to keep
    that profile identity intact instead of collapsing back to anonymous
    effective permissions. The session refresh path also needs to rebuild
    profile-derived network proxy state so active profile switches take
    effect consistently.
    
    ## What changed
    
    - Preserve the active permission profile through session updates.
    - Rebuild profile-derived runtime/network configuration when the active
    profile changes.
    - Keep the runtime path aligned with the current session configuration
    APIs.
    - Tighten the affected tests, including the Windows delete-pending
    memory-file case that was intermittently tripping CI.
    
    ## Stack
    
    1. **This PR**: runtime/session/network propagation for active
    permission profiles.
    2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
    plumbing and guardrail flow.
    3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
    `/permissions` menu and custom profile display.
    
    <img width="1296" height="906" alt="image"
    src="https://github.com/user-attachments/assets/077fa3a7-80cb-4925-80b1-d2395018d90a"
    />
  • feat: expose turn-start metadata to extensions (#23688)
    ## Why
    
    The goal extension needs more context when a turn starts than
    `turn_store` alone provides.
    
    In particular, goal accounting needs the stable turn id, the effective
    collaboration mode, and the cumulative token-usage baseline captured at
    turn start so it can:
    
    - suppress goal accounting for plan-mode turns
    - compute exact per-turn deltas from cumulative `total_token_usage`
    snapshots instead of relying on the most recent usage event alone
    - keep the extension-owned accounting path aligned with the host turn
    lifecycle
    
    ## What
    
    - extend `codex_extension_api::TurnStartInput` to expose `turn_id`,
    `collaboration_mode`, and `token_usage_at_turn_start`
    - pass the full `TurnContext` plus the captured token-usage baseline
    through the turn-start lifecycle emission path
    - initialize goal turn accounting from the turn-start baseline and
    collaboration mode
    - switch goal token accounting to compute deltas from cumulative
    `total_token_usage` snapshots
    - add coverage for the new turn-start lifecycle fields and for
    goal-accounting baseline behavior
    
    ## Testing
    
    - added `turn_start_lifecycle_exposes_turn_metadata_and_token_baseline`
    in `codex-rs/core/src/session/tests.rs`
    - added `ext/goal/tests/accounting.rs` coverage for baseline-aware goal
    accounting and plan-mode suppression
  • [codex] Preserve steer input as user input (#23405)
    ## Why
    
    Steered input was queued as a `ResponseInputItem`, then parsed back into
    a user message before recording. That path loses information that only
    exists on `UserInput`, such as UI text elements.
    
    This change keeps turn-local pending input typed as either original
    `UserInput` or existing response items, so steered user input reaches
    user-message recording without being reconstructed from a response item.
    
    ## What changed
    
    - Add `TurnInput` for active-turn pending input.
    - Queue `Session::steer_input` as `TurnInput::UserInput`.
    - Run pending-input hook inspection only for `TurnInput::UserInput`.
    - Process drained pending input item by item: accepted items are
    recorded, blocked items append hook context and are skipped.
    - Remove the pending-input prepend/requeue path.
    
    ## Validation
    
    - `just fmt`
    - `just fix -p codex-core`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core --lib
    session::tests::task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input
    -- --nocapture`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core --lib steer_input`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core --lib pending_input`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core --test all
    pending_input`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core` (unit tests passed:
    1835 passed, 0 failed, 4 ignored; integration `all` target failed due
    missing helper binaries such as `codex`/`test_stdio_server` plus
    unrelated MCP/search/code-mode expectations)
  • [codex] Move pending input into input queue (#22728)
    ## Why
    
    Pending model input was split across `Session`, `TurnState`, and the
    agent mailbox. That made it easy for new paths to manage queued user
    input or mailbox delivery outside the intended ownership boundary.
    
    This PR consolidates the model-facing input lifecycle behind the session
    input queue so turn-local pending input, next-turn queued items, and
    mailbox delivery coordination are owned in one place.
    
    ## What Changed
    
    - Added `session/input_queue.rs` to own pending input queues and mailbox
    delivery coordination.
    - Removed the standalone `agent/mailbox.rs` channel wrapper and store
    mailbox items directly in the input queue.
    - Moved pending-input mutations off `TurnState`; `TurnState` now exposes
    the queue-owned storage directly for now.
    - Routed abort cleanup, mailbox delivery phase changes, next-turn queued
    items, and active-turn pending input through `InputQueue`.
    - Boxed stack-heavy agent resume/fork startup futures that the refactor
    pushed over the default test stack.
    - Updated session, task, goal, stream-event, and multi-agent call sites
    and tests to use the new queue ownership.
    
    ## Verification
    
    - `cargo test -p codex-core --lib agent::control::tests`
    - `cargo test -p codex-core --lib
    agent::control::tests::resume_closed_child_reopens_open_descendants --
    --exact`
    - `cargo test -p codex-core --lib
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
    -- --exact`
    - `cargo test -p codex-core --lib
    agent::control::tests::resume_thread_subagent_restores_stored_nickname_and_role
    -- --exact`
    - `cargo test -p codex-core` was also run; it completed with 1814
    passed, 4 ignored, and one timeout in
    `agent::control::tests::resume_thread_subagent_restores_stored_nickname_and_role`,
    which passed when rerun in isolation.
  • goals: keep pause transitions explicit (#23088)
    ## Problem
    
    This addresses several user-reported cases where active goals were
    paused even though the user had not explicitly asked for that
    transition:
    
    - the guardian approval-review circuit breaker interrupted a turn and
    implicitly paused the goal
    - a shutdown in one app-server instance could pause a goal while a
    second instance was still actively running the same thread
    - steering-style interrupts could also pause the goal even though they
    are meant to redirect work, not stop the goal lifecycle
    
    The common problem was that core treated `TurnAbortReason::Interrupted`
    as an implicit request to transition the persisted goal to `paused`.
    That made unrelated interrupt paths mutate goal state as a side effect,
    and in the multi-app-server case it allowed stale process teardown to
    pause a live goal owned by another running client.
    
    After this change, transitioning a goal to `paused` is always an
    explicit action performed by a client or another intentional goal-state
    mutation. It is never an implicit transition triggered by generic
    interrupt handling.
    
    Refs #22884.
    
    ## What changed
    
    - Remove the goal runtime path that paused active goals after
    interrupted task aborts.
    - Drop the now-unused abort reason from `GoalRuntimeEvent::TaskAborted`.
    - Update the focused regression coverage so an interrupted active goal
    still accounts usage but remains `active`.
  • Make extension lifecycle hooks async (#23291)
    ## Why
    
    Extension lifecycle hooks sit on the host/extension boundary, but the
    current trait surface only allows synchronous callbacks. That forces
    extensions that need to seed, rehydrate, observe, or flush
    extension-owned state during thread and turn transitions to either block
    inside the callback or move async work into separate host plumbing.
    
    This PR makes those lifecycle callbacks awaitable so extension
    implementations can perform async work directly at the lifecycle point
    where the host already has the relevant session, thread, or turn stores
    available.
    
    ## What changed
    
    - Makes `ThreadLifecycleContributor` and `TurnLifecycleContributor`
    async in `codex-extension-api`.
    - Awaits thread start/resume/stop and turn start/stop/abort lifecycle
    callbacks from `codex-core`.
    - Updates the guardian and memories extensions to implement the async
    lifecycle trait surface.
    - Updates the existing lifecycle tests to use async contributor
    implementations.
    - Adds `async-trait` to the crates that now expose or implement these
    async object-safe lifecycle traits.
    
    ## Testing
    
    - Existing `codex-core` lifecycle tests were updated to cover async
    implementations for thread stop and turn abort ordering.
  • Fix turn extension data task plumbing (#22646)
    ## Summary
    - carry the per-turn extension data through RunningTask so abort
    handling can rebuild SessionTaskContext
    - update stale test ExtensionData::new() callsites to pass the turn id
    
    ## Testing
    - Not run after PR branch creation; CI will cover.
  • Wire turn item contributors into stream output (#22494)
    ## Summary
    - run registered TurnItemContributor hooks for parsed stream output
    items
    - plumb the active turn extension store into stream item handling
    - preserve existing memory citation parsing as fallback after
    contributors run
    
    ## Tests
    - cargo test -p codex-core stream_events_utils -- --nocapture
    - just fmt
    - just fix -p codex-core
    - git diff --check
  • Deprecate TurnContext cwd and resolve_path (#22519)
    ## Why
    
    `TurnContext::cwd` and `TurnContext::resolve_path` are being phased out
    in favor of using the selected turn environment cwd directly.
    Deprecating both APIs makes any new direct dependency visible while
    preserving the existing migration path for current callers.
    
    ## What Changed
    
    - Marked `TurnContext::cwd` and `TurnContext::resolve_path` as
    deprecated with guidance to use the selected turn environment cwd
    instead.
    - Added exact `#[allow(deprecated)]` suppressions at each existing
    direct usage site, including tests, rather than adding crate-wide
    suppression.
    - Kept the change behavior-preserving: current cwd reads, writes, and
    path resolution continue to use the same values.
    
    ## Verification
    
    - `just fmt`
    - `cargo check -p codex-core`
    - `cargo check -p codex-core --tests`
    - `git diff --check`
  • feat: move extension scope ids into ExtensionData (#22490)
    ## Summary
    - add a scoped level_id to ExtensionData and expose it through
    level_id()
    - remove thread_id/turn_id parameters from extension contributor inputs
    where the scoped ExtensionData already carries that identity
    - move turn-scoped extension data onto TurnContext so token usage and
    lifecycle contributors can share the same turn store
    
    ## Testing
    - cargo check -p codex-extension-api -p codex-core --tests
    - cargo test -p codex-extension-api
    - cargo test -p codex-guardian
    - cargo test -p codex-core --lib
    record_token_usage_info_notifies_extension_contributors
    - cargo test -p codex-core --lib
    submission_loop_channel_close_emits_thread_stop_lifecycle
    - cargo test -p codex-core --lib
    submission_loop_channel_close_aborts_active_turn_before_thread_stop_lifecycle
    - just fix -p codex-extension-api
    - just fix -p codex-guardian
    - just fix -p codex-core
    - just fmt
    
    ## Note
    - Attempted cargo test -p codex-core; it aborted in
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
    with the existing stack overflow before the full suite completed.
  • feat: add turn lifecycle contributors (#22480)
    ## Why
    
    Extensions can already contribute prompt, tool, turn-item, and
    thread-lifecycle behavior, but there was no explicit host-owned hook for
    per-turn setup and cleanup. That makes extension-private turn state
    awkward: an extension either has to stash it outside the turn lifecycle
    or depend on core runtime objects.
    
    This adds a small turn lifecycle boundary. Extensions receive stable
    identifiers plus the existing session, thread, and turn `ExtensionData`
    stores, while core keeps owning task scheduling, cancellation, and turn
    teardown.
    
    ## What Changed
    
    - Added `TurnLifecycleContributor` with `on_turn_start`, `on_turn_stop`,
    and `on_turn_abort` callbacks in `codex-rs/ext/extension-api`.
    - Added typed `TurnStartInput`, `TurnStopInput`, and `TurnAbortInput`
    payloads that expose `thread_id`, `turn_id`, `session_store`,
    `thread_store`, and `turn_store`.
    - Registered and re-exported turn lifecycle contributors through
    `ExtensionRegistry` and `ExtensionRegistryBuilder`.
    - Wired `Session` to emit turn start, stop, and abort callbacks from the
    existing turn/task lifecycle paths.
    - Carried the turn-scoped `ExtensionData` through `RunningTask` and
    `RemovedTask` so stop/abort callbacks receive the same turn store
    created at turn start.
    
    ## Verification
    
    - Not run locally.
  • [codex-analytics] add item lifecycle timing (#20514)
    ## Why
    
    Tool families already disagree on what their existing `duration` fields
    mean, so lifecycle latency should live on the shared item envelope
    instead of being inferred from per-tool execution fields. Carrying that
    envelope through app-server notifications gives downstream consumers one
    reusable timing signal without pretending every tool has the same
    execution semantics.
    
    ## What changed
    
    - Adds `started_at_ms` to core `ItemStartedEvent` values and
    `completed_at_ms` to core `ItemCompletedEvent` values.
    - Populates those timestamps in the shared session lifecycle emitters,
    so protocol-native items get timing without each producer tracking its
    own clock state.
    - Exposes `startedAtMs` on app-server `item/started` notifications and
    `completedAtMs` on `item/completed` notifications.
    - Maps the lifecycle timestamps through the app-server boundary while
    leaving legacy-converted notifications nullable when no lifecycle
    timestamp exists.
    - Regenerates the app-server JSON schema and TypeScript fixtures for the
    notification-envelope change and updates downstream fixtures that
    construct those notifications directly.
    - Extends the existing web-search and image-generation integration flows
    to assert the new lifecycle timestamps on the native item events.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-tui -p codex-exec
    -p codex-app-server-client`
    - `cargo test -p codex-core --test all web_search_item_is_emitted`
    - `cargo test -p codex-core --test all
    image_generation_call_event_is_emitted`
    - `cargo test -p codex-app-server-protocol`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20514).
    * #18748
    * #18747
    * #17090
    * #17089
    * __->__ #20514
  • Add reasoning effort to turn tracing spans (#20060)
    Why
    #19432 added token usage to the turn and response spans. This follow-up
    adds the configured reasoning effort so performance traces can be
    filtered by model effort.
    
    [example
    trace](https://openai.datadoghq.com/apm/trace/1ff708a87159ff4898bdc8bd6091ec18?graphType=waterfall&shouldShowLegend=true&spanID=6596351544047485652&traceQuery=)
    <img width="533" height="434" alt="Screenshot 2026-04-28 at 3 52 12 PM"
    src="https://github.com/user-attachments/assets/77ef32fc-d7cd-4eec-87b4-26c6798f1af8"
    />
    
    
    What Changed
    - Adds `codex.turn.reasoning_effort` to the turn span.
    - Adds `codex.request.reasoning_effort` to `handle_responses`.
    - Extends the span test to cover explicit `high` effort with token
    usage.
    
    Testing
    - `cargo test -p codex-core
    turn_and_completed_response_spans_record_token_usage`
    - `cargo test -p codex-otel`
    - `just fmt`
    - `just fix -p codex-core`
    - `just fix -p codex-otel`
  • feat: add remote compaction v2 Responses client path (#20773)
    ## Why
    
    This adds the `remote_compaction_v2` client path so remote compaction
    can run through the normal Responses stream and install a
    `context_compaction` item that trigger a compaction.
    
    The goal is to migrate some of the compaction logic on the client side
    
    We keeps the v2 transport behind a feature flag while letting follow-up
    requests reuse the compacted context instead of falling back to the
    legacy compaction item shape.
    
    ## What changed
    
    - add `ResponseItem::ContextCompaction` and refresh the generated
    app-server / schema / TypeScript fixtures that expose response items on
    the wire
    - add `core/src/compact_remote_v2.rs` to send compaction through the
    standard streamed Responses client, require exactly one
    `context_compaction` output item, and install that item into compacted
    history
    - route manual compact and auto-compaction through the v2 path when
    `remote_compaction_v2` is enabled, while keeping the existing remote
    compaction path as the fallback
    - preserve the new item type across history retention, follow-up request
    construction, telemetry, rollout persistence, and rollout-trace
    normalization
    - add targeted coverage for the feature flag, `context_compaction`
    serialization, rollout-trace normalization, and remote-compaction
    follow-up behavior
    
    ## Verification
    
    - added protocol tests for `context_compaction`
    serialization/deserialization in `protocol/src/models.rs`
    - added rollout-trace coverage for `context_compaction` normalization in
    `rollout-trace/src/reducer/conversation_tests.rs`
    - added remote compaction integration coverage for v2 follow-up reuse
    and mixed compaction output streams in
    `core/tests/suite/compact_remote.rs`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove no-tool goal continuation suppression (#20523)
    ## Why
    
    `/goal` is supposed to keep Codex working until the goal is actually
    done. The previous continuation logic had two ways to stop early: the
    continuation prompt told the model to wait for new input when it felt
    blocked, and the runtime suppressed another continuation turn after a
    continuation finished without any tool calls.
    
    That made goals stop short even when the agent could still keep making
    progress (I received a few reports of this from users). It also relied
    on a brittle heuristic that treated "no registry tool calls" as
    equivalent to "should stop."
    
    ## What changed
    
    - removed the continuation prompt sentence that told the model to stop
    and wait for new input when it could not continue productively
    - removed the goal runtime suppression heuristic that stopped
    auto-continuation after a no-tool continuation turn
    - deleted the continuation-activity bookkeeping and left `tool_calls` as
    telemetry only
    - added focused regressions for the two intended behaviors: completed
    no-tool continuation turns still continue, while `request_user_input`
    keeps the existing turn open instead of spawning a new continuation
  • [codex] Remove unused event messages (#20511)
    ## Why
    
    Several legacy `EventMsg` variants were still emitted or mapped even
    though clients either ignored them or had moved to item/lifecycle
    events. `Op::Undo` had also degraded to an unavailable shim, so this
    removes that dead task path instead of preserving a command that cannot
    do useful work.
    
    `McpStartupComplete`, `WebSearchBegin`, and `ImageGenerationBegin` are
    intentionally kept because useful consumers still depend on them: MCP
    startup completion drives readiness behavior, and the begin events let
    app-server/core consumers surface in-progress web-search and
    image-generation items before the final payload arrives.
    
    ## What Changed
    
    - Removed weak legacy event variants and payloads from `codex-protocol`,
    including legacy agent deltas, background events, and undo lifecycle
    events.
    - Kept/restored `EventMsg::McpStartupComplete`,
    `EventMsg::WebSearchBegin`, and `EventMsg::ImageGenerationBegin` with
    serializer and emission coverage.
    - Updated core, rollout, MCP server, app-server thread history,
    review/delegate filtering, and tests to rely on the useful replacement
    events that remain.
    - Removed `Op::Undo`, `UndoTask`, the undo test module, and stale TUI
    slash-command comments.
    - Stopped agent job/background progress and compaction retry notices
    from emitting `BackgroundEvent` payloads.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-app-server-protocol -p
    codex-core -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-protocol -p codex-app-server-protocol -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-core --test all suite::items`
    - `just fix -p codex-protocol -p codex-app-server-protocol -p codex-core
    -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - Earlier coverage on this PR also included `codex-mcp`, `codex-tui`,
    core library tests, MCP/plugin/delegate/review/agent job tests, and MCP
    startup TUI tests.
  • Gate multi-agent v2 tools independently of collab (#20246)
    ## Why
    
    `multi_agents_v2` is meant to be independently gated from the older
    `collab` feature. The tool registry still treated the
    collaboration-style agent tools as `collab`-only, so enabling
    `multi_agents_v2` without `collab` omitted the v2 agent tools. Review
    and guardian sub-sessions also need to keep agent spawning disabled even
    when the outer session has `multi_agents_v2` enabled.
    
    ## What changed
    
    - Include the collab-backed agent tools when either `multi_agents_v2` or
    `collab` is enabled.
    - Explicitly disable `multi_agents_v2` for review and guardian review
    sub-sessions, matching the existing `spawn_csv` and `collab`
    restrictions.
    - Add a registry test that enables `multi_agents_v2`, disables `collab`,
    and verifies the v2 agent tools are present while legacy `send_input`
    and `resume_agent` remain hidden.
    
    ## Testing
    
    - Added
    `test_build_specs_multi_agent_v2_does_not_require_collab_feature`.
  • [codex] Add token usage to turn tracing spans (#19432)
    ## Why
    
    Slow Codex turns are easier to debug when token usage is visible in the
    trace itself, without joining against separate analytics. This adds
    token usage to existing turn-handling spans for regular user turns only.
    
    [Example
    turn](https://openai.datadoghq.com/apm/trace/9d353efa2cb5de1f4c5b93dc33c3df04?colorBy=service&graphType=flamegraph&shouldShowLegend=true&sort=time&spanID=3555541504891512675&spanViewType=metadata&traceQuery=)
    <img width="1447" height="967" alt="Screenshot 2026-04-24 at 3 03 07 PM"
    src="https://github.com/user-attachments/assets/ab7bb187-e7fc-41f0-a366-6c44610b2b2c"
    />
    
    ## What Changed
    
    Added response-level token fields on completed handle_responses spans:
    
    gen_ai.usage.input_tokens
    gen_ai.usage.cache_read.input_tokens
    gen_ai.usage.output_tokens
    codex.usage.reasoning_output_tokens
    codex.usage.total_tokens
    Added aggregate token fields on regular turn spans:
    
    codex.turn.token_usage.*
    Added an explicit regular-turn opt-in via
    SessionTask::records_turn_token_usage_on_span() so this is not coupled
    to span-name strings.
    
    ## Testing
    
    - `cargo test -p codex-otel`
    - `cargo test -p codex-core
    turn_and_completed_response_spans_record_token_usage`
    - `just fmt`
    - `just fix -p codex-core`
    - `just fix -p codex-otel`
    - Manual local Electron/app-server smoke test: regular user turn emits
    the new span fields
    
    Known status: `cargo test -p codex-core` was attempted and failed in
    unrelated existing areas: config approvals, request-permissions,
    git-info ordering, and subagent metadata persistence.
  • Add turn start timestamp to turn metadata (#19473)
    ## Why
    - Without change: MCP tool calls receive
    `_meta["x-codex-turn-metadata"]` with `session_id` and `turn_id`.
    - Issue: MCP servers may want the turn start timestamp to measure
    internal latency relative to turn start.
    
    ## What Changed
    - With change: turn metadata now includes `turn_started_at_unix_ms`,
    which is propagated to MCP tool calls in
    `_meta["x-codex-turn-metadata"]`.
    
    ## Verification
    - `codex-rs/core/src/mcp_tool_call_tests.rs`
    - `codex-rs/core/src/turn_metadata_tests.rs`
    - `codex-rs/core/src/turn_timing_tests.rs`
    - `codex-rs/core/tests/responses_headers.rs`
    - `codex-rs/core/tests/suite/search_tool.rs`
  • Remove ghost snapshots (#19481)
    ## Summary
    - Remove `ghost_snapshot` / `GhostCommit` from the Responses API surface
    and generated SDK/schema artifacts.
    - Keep legacy config loading compatible, but make undo a no-op that
    reports the feature is unavailable.
    - Clean up core history, compaction, telemetry, rollout, and tests to
    stop carrying ghost snapshot items.
    
    ## Testing
    - Unit tests passed for `codex-protocol`, `codex-core` targeted undo and
    compaction flows, `codex-rollout`, and `codex-app-server-protocol`.
    - Regenerated config and app-server schemas plus Python SDK artifacts
    and verified they match the checked-in outputs.
  • permissions: derive legacy exec policies at boundaries (#19737)
    ## Why
    
    After config and requirements store canonical profiles, exec requests
    should not cache a derived `SandboxPolicy`. The cached legacy value can
    drift from the richer profile state, and most execution paths already
    have the filesystem and network runtime policies they need.
    
    ## What Changed
    
    - Removes `sandbox_policy` from `codex_sandboxing::SandboxExecRequest`
    and `codex_core::sandboxing::ExecRequest`.
    - Adds an on-demand `ExecRequest::compatibility_sandbox_policy()` helper
    for the Windows and legacy call sites that still need a `SandboxPolicy`
    projection.
    - Updates Windows filesystem override setup and unified exec policy
    serialization to derive that compatibility policy at the boundary.
    - Updates Unix escalation reruns and direct shell requests to
    reconstruct exec requests from `PermissionProfile` plus runtime
    filesystem/network policy, without carrying a cached legacy policy.
    - Adjusts sandboxing manager tests to assert the effective profile
    rather than the removed legacy field.
    
    ## Verification
    
    - `cargo check -p codex-config -p codex-core -p codex-sandboxing -p
    codex-app-server -p codex-cli -p codex-tui`
    - `cargo test -p codex-sandboxing manager`
    - `cargo test -p codex-core
    exec_server_params_use_env_policy_overlay_contract`
    - `cargo test -p codex-core unix_escalation`
    - `cargo test -p codex-core exec::tests`
    - `cargo test -p codex-core sandboxing::tests`
  • permissions: migrate approval and sandbox consumers to profiles (#19393)
    ## Why
    
    Runtime decisions should not infer permissions from the lossy legacy
    sandbox projection once `PermissionProfile` is available. In particular,
    `Disabled` and `External` need to remain distinct, and managed profiles
    with split filesystem or deny-read rules should not be collapsed before
    approval, network, safety, or analytics code makes decisions.
    
    ## What Changed
    
    - Changes managed network proxy setup and network approval logic to use
    `PermissionProfile` when deciding whether a managed sandbox is active.
    - Migrates patch safety, Guardian/user-shell approval paths, Landlock
    helper setup, analytics sandbox classification, and selected
    turn/session code to profile-backed permissions.
    - Validates command-level profile overrides against the constrained
    `PermissionProfile` rather than a strict `SandboxPolicy` round trip.
    - Preserves configured deny-read restrictions when command profiles are
    narrowed.
    - Adds coverage for profile-backed trust, network proxy/approval
    behavior, patch safety, analytics classification, and command-profile
    narrowing.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19393).
    * #19395
    * #19394
    * __->__ #19393
  • permissions: make runtime config profile-backed (#19606)
    ## Why
    
    This supersedes #19391. During stack repair, GitHub marked #19391 as
    merged into a temporary stack branch rather than into `main`, so the
    runtime-config change needed a fresh PR.
    
    `PermissionProfile` is now the canonical permissions shape after #19231
    because it can distinguish `Managed`, `Disabled`, and `External`
    enforcement while also carrying filesystem rules that legacy
    `SandboxPolicy` cannot represent cleanly. Core config and session state
    still needed to accept profile-backed permissions without forcing every
    profile through the strict legacy bridge, which rejected valid runtime
    profiles such as direct write roots.
    
    The unrelated CI/test hardening that previously rode along with this PR
    has been split into #19683 so this PR stays focused on the permissions
    model migration.
    
    ## What Changed
    
    - Adds `Permissions.permission_profile` and
    `SessionConfiguration.permission_profile` as constrained runtime state,
    while keeping `sandbox_policy` as a legacy compatibility projection.
    - Introduces profile setters that keep `PermissionProfile`, split
    filesystem/network policies, and legacy `SandboxPolicy` projections
    synchronized.
    - Uses a compatibility projection for requirement checks and legacy
    consumers instead of rejecting profiles that cannot round-trip through
    `SandboxPolicy` exactly.
    - Updates config loading, config overrides, session updates, turn
    context plumbing, prompt permission text, sandbox tags, and exec request
    construction to carry profile-backed runtime permissions.
    - Preserves configured deny-read entries and `glob_scan_max_depth` when
    command/session profiles are narrowed.
    - Adds `PermissionProfile::read_only()` and
    `PermissionProfile::workspace_write()` presets that match legacy
    defaults.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19606).
    * #19395
    * #19394
    * #19393
    * #19392
    * __->__ #19606
  • Add goal core runtime (4 / 5) (#18076)
    Adds the core runtime behavior for active goals on top of the model
    tools from PR 3.
    
    ## Why
    
    A long-running goal should be a core runtime concern, not something
    every client has to implement. Core owns the turn lifecycle, tool
    completion boundaries, interruptions, resume behavior, and token usage,
    so it is the right place to account progress, enforce budgets, and
    decide when to continue work.
    
    ## What changed
    
    - Centralized goal lifecycle side effects behind
    `Session::goal_runtime_apply(GoalRuntimeEvent::...)`.
    - Starts goal continuation turns only when the session is idle; pending
    user input and mailbox work take priority.
    - Accounts token and wall-clock usage at turn, tool, mutation,
    interrupt, and resume boundaries; `get_thread_goal` remains read-only.
    - Preserves sub-second wall-clock remainder across accounting boundaries
    so long-running goals do not drift downward over time.
    - Treats token budget exhaustion as a soft stop by marking the goal
    `budget_limited` and injecting wrap-up steering instead of aborting the
    active turn.
    - Suppresses budget steering when `update_goal` marks a goal complete.
    - Pauses active goals on interrupt and auto-reactivates paused goals
    when a thread resumes outside plan mode.
    - Suppresses repeated automatic continuation when a continuation turn
    makes no tool calls.
    - Added continuation and budget-limit prompt templates.
    
    ## Verification
    
    - Added focused core coverage for continuation scheduling, accounting
    boundaries, budget-limit steering, completion accounting, interrupt
    pause behavior, resume auto-activation, and wall-clock remainder
    accounting.
  • Add agents.interrupt_message for interruption markers (#19351)
    ## Why
    
    Agent interruptions currently always persist a model-visible
    interrupted-turn marker before emitting `TurnAborted`. That marker is
    useful by default because it gives the next model turn context about a
    deliberately interrupted task, but some deployments need to suppress
    that history injection entirely while still keeping the client-visible
    interruption event.
    
    ## What changed
    
    - Add `[agents] interrupt_message = false` to disable the model-visible
    interrupted-turn marker.
    - Resolve the setting into `Config::agent_interrupt_message_enabled`,
    defaulting to `true` so existing behavior is unchanged.
    - Apply the setting to both live interrupted turns and interrupted fork
    snapshots.
    - Keep emitting `TurnAborted` even when the history marker is disabled.
    - Regenerate `core/config.schema.json` for the new
    `agents.interrupt_message` field.
    
    ## Testing
    
    - `cargo test -p codex-core load_config_resolves_agent_interrupt_message
    -- --nocapture`
    - `cargo test -p codex-core
    disabled_interrupted_fork_snapshot_appends_only_interrupt_event --
    --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_interrupted_marker_uses_developer_input_message --
    --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_followup_task_can_disable_interrupted_marker --
    --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message
    -- --nocapture`
    - `cargo check -p codex-core`
  • Make MultiAgentV2 interruption markers assistant-authored (#19124)
    ## Why
    
    `MultiAgentV2` follow-up messages are delivered to agents as
    assistant-authored `InterAgentCommunication` envelopes. When
    `followup_task` used `interrupt: true`, the interrupted-turn guidance
    was still persisted as a contextual user message, so model-visible
    history made a system-generated interruption boundary look
    user-authored.
    
    This keeps interruption guidance consistent with the rest of the v2
    inter-agent message stream while preserving the legacy marker shape for
    non-v2 sessions.
    
    ## What changed
    
    - Make `interrupted_turn_history_marker` feature-aware.
    - Record the interrupted-turn marker as an assistant `OutputText`
    message when `Feature::MultiAgentV2` is enabled.
    - Keep the existing user contextual fragment for non-v2 sessions.
    - Apply the same feature-aware marker to interrupted fork snapshots.
    - Add coverage for the live `followup_task` interrupt path and the
    helper-level v2 marker shape.
    
    ## Testing
    
    - `cargo test -p codex-core
    multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message
    -- --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_interrupted_marker_uses_assistant_output_message --
    --nocapture`
    - `cargo test -p codex-core interrupted_fork_snapshot -- --nocapture`
  • feat: let model providers own model discovery (#18950)
    ## Why
    
    `codex-models-manager` had grown to own provider-specific concerns:
    constructing OpenAI-compatible `/models` requests, resolving provider
    auth, emitting request telemetry, and deciding how provider catalogs
    should be sourced. That made the manager harder to reuse for providers
    whose model catalog is not fetched from the OpenAI `/models` endpoint,
    such as Amazon Bedrock.
    
    This change moves provider-specific model discovery behind
    provider-owned implementations, so the models manager can focus on
    refresh policy, cache behavior, picker ordering, and model metadata
    merging.
    
    ## What Changed
    
    - Introduced a `ModelsManager` trait with separate `OpenAiModelsManager`
    and `StaticModelsManager` implementations.
    - Added `ModelsEndpointClient` so OpenAI-compatible HTTP fetching lives
    outside `codex-models-manager`.
    - Moved `/models` request construction, provider auth resolution,
    timeout handling, and request telemetry into `codex-model-provider` via
    `OpenAiModelsEndpoint`.
    - Added provider-owned `models_manager(...)` construction so configured
    OpenAI-compatible providers use `OpenAiModelsManager`, while
    static/catalog-backed providers can return `StaticModelsManager`.
    - Added an Amazon Bedrock static model catalog for the GPT OSS Bedrock
    model IDs.
    - Updated core/session/thread manager code and tests to depend on
    `Arc<dyn ModelsManager>`.
    - Moved offline model test helpers into
    `codex_models_manager::test_support`.
    ## Metadata References
    
    The Bedrock catalog metadata is based on the official Amazon Bedrock
    OpenAI model documentation:
    
    - [Amazon Bedrock OpenAI
    models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-openai.html)
    lists the Bedrock model IDs, text input/output modalities, and `128,000`
    token context window for `gpt-oss-20b` and `gpt-oss-120b`.
    - [Amazon Bedrock `gpt-oss-120b` model
    card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-oss-120b.html)
    lists the `bedrock-runtime` model ID `openai.gpt-oss-120b-1:0`, the
    `bedrock-mantle` model ID `openai.gpt-oss-120b`, text-only modalities,
    and `128K` context window.
    - [OpenAI `gpt-oss-120b` model
    docs](https://developers.openai.com/api/docs/models/gpt-oss-120b)
    document configurable reasoning effort with `low`, `medium`, and `high`,
    plus text input/output modality.
    
    The display names, default reasoning effort, and priority ordering are
    Codex-local catalog choices.
    
    ## Test Plan
    - Manually verified app-server model listing with an AWS profile:
    
    ```shell
    CODEX_HOME="$(mktemp -d)" cargo run -p codex-app-server-test-client -- \
      --codex-bin ./target/debug/codex \
      -c 'model_provider="amazon-bedrock"' \
      -c 'model_providers.amazon-bedrock.aws.profile="codex-bedrock"' \
      -c 'model_providers.amazon-bedrock.aws.region="us-west-2"' \
      model-list
    ```
    
    The response returned the Bedrock catalog with `openai.gpt-oss-120b-1:0`
    as the default model and `openai.gpt-oss-20b-1:0` as the second listed
    model, both text-only and supporting low/medium/high reasoning effort.
  • feat(auto-review) short-circuit (#18890)
    ## Summary
    Short circuit the convo if auto-review hits too many denials
    
    ## Testing
    - [x] Added unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex-analytics] guardian review TTFT plumbing and emission (#17696)
    ## Why
    
    Guardian analytics includes time-to-first-token, but the Guardian
    reviewer runs as a normal Codex session and `TurnCompleteEvent` did not
    expose TTFT. The timing needs to flow through the standard
    turn-completion protocol so Guardian review analytics can consume the
    same value as the rest of the session machinery.
    
    ## What changed
    
    Adds optional `time_to_first_token_ms` to `TurnCompleteEvent` and
    populates it from `TurnTiming`. The value is carried through app-server
    thread history, rollout reconstruction, TUI/app-server adapters, and
    Guardian review session handling.
    
    Guardian review analytics now captures TTFT from the reviewer
    turn-complete event when available. Existing tests and fixtures are
    updated to set the new optional field to `None` where TTFT is not
    relevant.
    
    ## Verification
    
    - `cargo clippy -p codex-tui --tests -- -D warnings`
    - `cargo clippy -p codex-core --lib --tests -- -D warnings`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17696).
    * __->__ #17696
    * #17695
    * #17693
    * #18278
    * #18953
  • Add Windows sandbox unified exec runtime support (#15578)
    ## Summary
    
    This is the runtime/foundation half of the Windows sandbox unified-exec
    work.
    
    - add Windows sandbox `unified_exec` session support in
    `windows-sandbox-rs` for both:
      - the legacy restricted-token backend
      - the elevated runner backend
    - extend the PTY/process runtime so driver-backed sessions can support:
      - stdin streaming
      - stdout/stderr separation
      - exit propagation
      - PTY resize hooks
    - add Windows sandbox runtime coverage in `codex-windows-sandbox` /
    `codex-utils-pty`
    
    This PR does **not** enable Windows sandbox `UnifiedExec` for product
    callers yet because hooking this up to app-server comes in the next PR.
    
    Windows sandbox advertising is intentionally kept aligned with `main`,
    so sandboxed Windows callers still fall back to `ShellCommand`.
    
    This PR isolates the runtime/session layer so it can be reviewed
    independently from product-surface enablement.
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • Organize context fragments (#18794)
    Organize context fragments under `core/context`. Implement same trait on
    all of them.
  • Update models.json (#18586)
    - Replace the active models-manager catalog with the deleted core
    catalog contents.
    - Replace stale hardcoded test model slugs with current bundled model
    slugs.
    - Keep this as a stacked change on top of the cleanup PR.
  • feat: add metric to track the number of turns with memory usage (#18662)
    Add a metric `codex.turn.memory` to know if a turn used memories or not.
    This is not part of the other turn metrics as a label to limit
    cardinality
  • Move codex module under session (#18249)
    ## Summary
    - rename the core codex module root to session/mod.rs without using
    #[path]
    - move the codex module directory and tests under core/src/session
    - remove session/mod.rs reexports so call sites use explicit child
    module paths
    
    ## Testing
    - cargo test -p codex-core --lib
    - cargo check -p codex-core --tests
    - just fmt
    - just fix -p codex-core
    - git diff --check