Commit Graph

257 Commits

  • [codex] Remove external client session reset plumbing (#24157)
    ## Why
    
    The turn loop no longer needs to decide when a `ModelClientSession`
    should reset its websocket state after compaction. That reset behavior
    belongs inside the model client, where the websocket cache and retry
    state are owned. The repo guidance now calls this out explicitly so
    future changes let the incremental request logic decide whether the
    previous request can be reused.
    
    ## What Changed
    
    - Removed the `reset_client_session` return value from pre-sampling and
    auto-compact helpers in `core/src/session/turn.rs`.
    - Changed compaction helpers to return `CodexResult<()>` so callers only
    handle success or failure.
    - Made `ModelClientSession::reset_websocket_session` private to
    `core/src/client.rs`, leaving it callable only from model-client
    internals.
    - Added `AGENTS.md` guidance not to call `reset_client_session`
    unnecessarily.
    
    ## Validation
    
    - `just test -p codex-core session::turn`
  • [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`
  • otel: drop legacy profile usage telemetry (#24061)
    ## Summary
    - drop the dead legacy profile usage metric and active-profile
    conversation-start fields
    - update role comments so they describe provider and service-tier
    preservation without legacy config-profile wording
    - pair the code cleanup with the file-backed profile docs update in
    openai/developers-website#1476
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-otel`
    - `cargo test -p codex-core` *(fails: existing stack overflow in
    `mcp_tool_call::tests::guardian_mode_mcp_denial_returns_rationale_message`)*
    - `cargo test -p codex-core --lib
    mcp_tool_call::tests::guardian_mode_mcp_denial_returns_rationale_message`
    *(fails with the same stack overflow)*
  • config: remove legacy profile write paths (#24055)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2 and
    [#23886](https://github.com/openai/codex/pull/23886) removed CLI
    forwarding for the legacy profile-v1 path. Core and TUI config
    persistence still carried `active_profile` and
    `ConfigEditsBuilder::with_profile`, which let later writes continue
    targeting legacy `[profiles.<name>]` tables after profile selection
    moved to profile-v2 config files.
    
    ## What
    
    - Remove legacy profile routing from
    [`ConfigEditsBuilder`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/core/src/config/edit.rs#L1064-L1294),
    so core config edits no longer carry `with_profile` or infer
    `[profiles.*]` write targets from a `profile` key.
    - Drop `active_profile` plumbing from runtime `Config`, TUI
    startup/state, app-server config override forwarding, and Windows
    sandbox setup persistence.
    - Make app-server-backed TUI config edits use unscoped model,
    service-tier, feature, Auto-review, plan-mode, and Windows sandbox paths
    through
    [`tui/src/config_update.rs`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/tui/src/config_update.rs#L43-L112).
    - Update config edit coverage so legacy `profile` state stays untouched
    by direct model writes, and remove tests whose only contract was the
    deleted profile-scoped persistence path.
    
    ## Testing
    
    - Not run locally.
  • Remove plugin hooks feature flag (#22552)
    # Why
    
    This is a follow-up stacked on top of the `plugin_hooks` default-on
    change. Once we are comfortable making plugin hooks part of the normal
    plugin behavior, the separate feature flag stops buying us much and
    leaves extra branching/cache state behind.
    
    # What
    
    - remove the `PluginHooks` feature and generated config-schema entries
    - make plugin hook loading/listing follow plugin enablement directly
    - drop plugin-manager cache/state that only existed to distinguish
    hook-flag toggles
    - remove tests and fixtures that modeled `plugin_hooks = true/false`
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`
  • Honor client-resolved service tier defaults (#23537)
    ## Why
    
    Model catalog responses can now advertise a nullable
    `default_service_tier` for each model. Codex needs to preserve three
    distinct states all the way from config/app-server inputs to inference:
    
    - no explicit service tier, so the client may apply the current model
    catalog default when FastMode is enabled
    - explicit `default`, meaning the user intentionally wants standard
    routing
    - explicit catalog tier ids such as `priority`, `flex`, or future tiers
    
    Keeping those states distinct prevents the UI from showing one tier
    while core sends another, especially after model switches or app-server
    `thread/start` / `turn/start` updates.
    
    ## What Changed
    
    - Plumbed `default_service_tier` through model catalog protocol types,
    app-server model responses, generated schemas, model cache fixtures, and
    provider/model-manager conversions.
    - Added the request-only `default` service tier sentinel and normalized
    legacy config spelling so `fast` in `config.toml` still materializes as
    the runtime/request id `priority`.
    - Moved catalog default resolution to the TUI/client side, including
    recomputing the effective service tier when model/FastMode-dependent
    surfaces change.
    - Updated app-server thread lifecycle config construction so
    `serviceTier: null` preserves explicit standard-routing intent by
    mapping to `default` instead of internal `None`.
    - Kept core responsible for validating explicit tiers against the
    current model and stripping `default` before `/v1/responses`, without
    applying catalog defaults itself.
    
    ## Validation
    
    - `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
    - `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
    - `cargo test -p codex-tui service_tier`
    - `cargo test -p codex-protocol service_tier_for_request`
    - `cargo test -p codex-core get_service_tier`
    - `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
    service_tier`
  • Add SubagentStop hook (#22873)
    # What
    
    <img width="1792" height="1024" alt="image"
    src="https://github.com/user-attachments/assets/8f81d232-5813-4994-a61d-e42a05a93a3e"
    />
    
    `SubagentStop` runs when a thread-spawned subagent turn is about to
    finish. Thread-spawned subagents use `SubagentStop` instead of the
    normal root-agent `Stop` hook.
    
    Configured handlers match on `agent_type`. Hook input includes the
    normal stop fields plus:
    
    - `agent_id`: the child thread id.
    - `agent_type`: the resolved subagent type.
    - `agent_transcript_path`: the child subagent transcript path.
    - `transcript_path`: the parent thread transcript path.
    - `last_assistant_message`: the final assistant message from the child
    turn, when available.
    - `stop_hook_active`: `true` when the child is already continuing
    because an earlier stop-like hook blocked completion.
    
    `SubagentStop` shares the same completion-control semantics as `Stop`,
    scoped to the child turn:
    
    - No decision allows the child turn to finish.
    - `decision: "block"` with a non-empty `reason` records that reason as
    hook feedback and continues the child with that prompt.
    - `continue: false` stops the child turn. If `stopReason` is present,
    Codex surfaces it as the stop reason.
    
    # Lifecycle Scope
    
    Only thread-spawned subagents run `SubagentStop`.
    
    Internal/system subagents such as Review, Compact, MemoryConsolidation,
    and Other do not run normal `Stop` hooks and do not run `SubagentStop`.
    This avoids exposing synthetic matcher labels for internal
    implementation paths.
    
    # Stack
    
    1. #22782: add `SubagentStart`.
    2. This PR: add `SubagentStop`.
    3. #22882: add subagent identity to normal hook inputs.
  • 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"
    />
  • Support compact SessionStart hooks (#21272)
    # Why
    
    Compaction replaces the live conversation history, so hooks that use
    `SessionStart` to re-inject durable model context need a way to run
    again after that rewrite.
    
    Related - #19905 adds dedicated compact lifecycle hooks
    
    # What
    
    - add `compact` as a supported `SessionStart` source and matcher value
    - change pending `SessionStart` state from a single slot to a small FIFO
    queue so `resume` / `startup` / `clear` can be preserved alongside a
    later `compact`
    - drain all queued `SessionStart` sources before the next model request,
    preserving their original order
    
    # Testing
    
    The new integration coverage verifies both the basic `compact` matcher
    path and the stacked `resume` -> `compact` case where both hooks
    contribute `additionalContext` to the next model turn.
  • feat: account active goal progress in the goal extension (#23696)
    ## Why
    
    The goal extension can create and surface goals, but the live
    turn-accounting path still stopped short of persisting active-goal
    progress. That leaves token and wall-clock usage, plus
    `ThreadGoalUpdated` events, out of sync with the extension boundary once
    work actually advances or a goal transitions out of active state.
    
    ## What changed
    
    - Teach `GoalAccountingState` to track the current turn, active goal,
    token deltas, and wall-clock progress snapshots against the persisted
    goal id.
    - Flush active-goal accounting from tool-finish, turn-stop, and
    turn-abort lifecycle hooks, and emit `ThreadGoalUpdated` events when
    persisted progress changes.
    - Route `create_goal` and `update_goal` through the same accounting
    state so new goals start from the right baseline, final progress is
    flushed before status changes, and `update_goal` can mark a goal
    `blocked` as well as `complete`.
    - Keep budget-limited goals accruing through the end of the turn while
    clearing local active-goal state once a turn or explicit update is
    finished.
    - Expand backend and lifecycle coverage around store ids, baseline
    reset, tool-finish accounting, budget-limited carry-through, and
    blocked-goal updates.
    
    ## Testing
    
    - Added focused backend coverage in
    `codex-rs/ext/goal/tests/goal_extension_backend.rs` for baseline reset,
    tool-finish accounting, budget-limited turns, and blocked-goal updates.
    - Extended `codex-rs/core/src/session/tests.rs` to assert that lifecycle
    inputs expose the expected session, thread, and turn store ids.
  • 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
  • feat: rename 3 (#23669)
    Just a mechanical renaming
  • Warn on invalid UTF-8 in AGENTS.md files (#23232)
    Fixes #23223.
    
    ## Why
    
    Malformed AGENTS instructions should not fail silently. The reported
    issue had invalid UTF-8 in a global `AGENTS.md`; before this change,
    Codex treated that decode failure like a missing file, so the personal
    instructions disappeared without a user-visible explanation and the
    rollout had no `# AGENTS.md instructions` block.
    
    Project-level AGENTS files already used lossy decoding, so their
    instructions still appeared, but invalid bytes were replaced without
    telling the user. Global and project AGENTS files should behave
    consistently: keep usable instruction text when possible, and surface a
    diagnostic when bytes had to be replaced.
    
    ## What changed
    
    Global `AGENTS.override.md` and `AGENTS.md` loading now reads bytes and
    decodes with replacement characters on invalid UTF-8, matching
    project-level AGENTS behavior. Both global and project AGENTS loading
    now emit a startup warning when invalid UTF-8 is found, and both keep
    the instruction text with invalid byte sequences replaced.
    
    Missing files, non-file candidates, empty files, and the existing
    `AGENTS.override.md` before `AGENTS.md` precedence keep their current
    behavior.
    
    ## How users see it
    
    The warnings flow through the existing startup warning surface.
    App-server clients receive config-time startup warnings as
    `configWarning` notifications during initialization, and thread startup
    emits startup warnings as thread-scoped `warning` notifications.
    
    Global AGENTS invalid UTF-8 warnings can appear on both surfaces.
    Project-level AGENTS invalid UTF-8 warnings are discovered while
    building thread instructions, so they appear as thread-scoped `warning`
    notifications. Clients that render warning notifications in the
    conversation surface show the message as a visible diagnostic instead of
    silently hiding or altering instructions.
  • Move plugin and skill warmup into session startup (#23535)
    ## Why
    
    Plugin and skill loading is useful as warmup and early validation, but
    session startup does not need to wait for that work before it can
    continue building the session. Keeping it on the serial startup path
    adds avoidable latency to every fresh thread start.
    
    We still want invalid skill configurations to show up quickly, and we
    want the warmup to exercise the same plugin and skill manager caches
    that the normal turn path uses.
    
    ## What changed
    
    - moved plugin and skill warmup into the session startup async path
    instead of eagerly awaiting it on the serial setup path
    - kept the warmup using the session's resolved filesystem/environment
    context so skill loading still sees the right roots
    - preserved early skill-load error logging so broken skill
    configurations still surface during startup
    - left the per-turn plugin and skill loading path unchanged, so turns
    still use the normal cached managers
    
    ## Testing
    
    - Not run locally; relying on CI for validation.
  • Split plugin install discovery into list and request tools (#23372)
    ## Summary
    - Add `list_available_plugins_to_install` as the inventory step for
    plugin and connector install suggestions.
    - Slim `request_plugin_install` so it only handles the actual
    elicitation, instead of carrying the full discoverable list in its
    prompt.
    - Emit send-time telemetry when an install elicitation is dispatched,
    including requested tool identity in the event payload.
    - Emit install-result telemetry through `SessionTelemetry`, including
    tool type, user response action, and completion status.
    - Update registration and tests to cover the new two-step flow while
    keeping the existing `tool_suggest` feature gate unchanged.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core request_plugin_install`
    - `cargo test -p codex-core list_available_plugins_to_install`
    - `cargo test -p codex-core
    install_suggestion_tools_can_be_registered_without_search_tool`
    - `cargo test -p codex-otel
    manager_records_plugin_install_suggestion_metric`
    - `cargo test -p codex-otel
    manager_records_plugin_install_elicitation_sent_metric`
    - `just fix -p codex-core`
    - `just fix -p codex-tools`
    - `just fix -p codex-otel`
    - `cargo check -p codex-core`
  • Make local environment optional in EnvironmentManager (#23369)
    ## Summary
    - make `EnvironmentManager` local environment/runtime paths optional
    - simplify constructor surface around snapshot materialization
    - rename local env accessors to `require_local_environment` /
    `try_local_environment`
    
    ## Validation
    - devbox Bazel build for touched crate surfaces
    - `//codex-rs/exec-server:exec-server-unit-tests`
    - `//codex-rs/app-server-client:app-server-client-unit-tests`
    - filtered touched `//codex-rs/core:core-unit-tests` cases
  • Add SubagentStart hook (#22782)
    # What
    
    `SubagentStart` runs once when Codex creates a thread-spawned subagent,
    before that child sends its first model request. Thread-spawned
    subagents use `SubagentStart` instead of the normal root-agent
    `SessionStart` hook.
    
    Configured handlers match on the subagent `agent_type`, using the same
    value passed to `spawn_agent`. When no agent type is specified, Codex
    uses the default agent type.
    
    Hook input includes the normal session-start fields plus:
    
    - `agent_id`: the child thread id.
    - `agent_type`: the resolved subagent type.
    
    `SubagentStart` may return `hookSpecificOutput.additionalContext`. That
    context is added to the child conversation before the first model
    request.
    
    # Lifecycle Scope
    
    Only thread-spawned subagents run `SubagentStart`.
    
    Internal/system subagents such as Review, Compact, MemoryConsolidation,
    and Other do not run normal `SessionStart` hooks and do not run
    `SubagentStart`. This avoids exposing synthetic matcher labels for
    internal implementation paths.
    
    Also the `SessionStart` hook no longer fires for subagents, this matches
    behavior with other coding agents' implementation
    
    # Stack
    
    1. This PR: add `SubagentStart`.
    2. #22873: add `SubagentStop`.
    3. #22882: add subagent identity to normal hook inputs.
  • Make deny canonical for filesystem permission entries (#23493)
    ## Why
    Filesystem permission profiles used `none` for deny-read entries, which
    is less direct than the action the entry actually represents. This
    change makes `deny` the canonical filesystem permission spelling while
    preserving compatibility for older configs that still send `none`.
    
    ## What changed
    - rename `FileSystemAccessMode::None` to `Deny`
    - serialize and generate schemas with `deny` as the canonical value
    - retain `none` only as a legacy input alias for temporary config
    compatibility
    - update filesystem glob diagnostics and regression coverage to use the
    canonical spelling
    - refresh config and app-server schema fixtures to match the new wire
    shape
    
    ## Validation
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core config_toml_deserializes_permission_profiles
    --lib`
    - `cargo test -p codex-core
    read_write_glob_patterns_still_reject_non_subpath_globs --lib`
    
    Earlier in the session, a broad `cargo test -p codex-core` run reached
    unrelated pre-existing failures in timing/snapshot/git-info tests under
    this environment; the targeted surfaces touched by this PR passed
    cleanly.
  • [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 hook request plumbing into hook runtime (#23388)
    ## Why
    
    `run_turn` was still hand-building hook payloads and lifecycle events
    for a couple of hook paths. Most hook call sites already delegate
    request construction and event emission to `hook_runtime`, which keeps
    turn orchestration focused on model-flow decisions rather than hook
    plumbing.
    
    This also keeps the legacy `after_agent` message extraction next to the
    legacy hook dispatch instead of leaving response-item walking in
    `run_turn`.
    
    ## What changed
    
    - Added `run_stop_hooks` in `hook_runtime` to build `StopRequest`, emit
    preview start events, run the hook, and emit completion events.
    - Added `run_legacy_after_agent_hook` in `hook_runtime` to build and
    dispatch the legacy `AfterAgent` hook payload, including extracting
    input messages from response items.
    - Updated `run_turn` to call the hook runtime helpers and keep only the
    resulting continuation/block/stop decisions inline.
    - Removed the repeated pending session-start hook check from the run
    loop.
    
    ## Validation
    
    - `cargo test -p codex-core hook_runtime`
  • [codex] Allow empty turn/start requests (#23409)
    ## Why
    
    `turn/start` already accepts an input array on the wire, including an
    empty array, but core treated empty input as a no-op before the turn
    could reach the model. App-server clients need to be able to start a
    real turn even when there is no new user message, for example to let the
    model proceed from existing thread context.
    
    ## What changed
    
    - Removed the `run_turn` early return that skipped empty-input turns
    when there was no pending input.
    - Kept empty active-turn steering rejected by moving the `steer_input`
    empty-input check until after core has determined whether there is an
    active regular turn.
    - Empty regular turns now refresh `previous_turn_settings` like other
    regular turns, so follow-up context injection state advances
    consistently.
    - Added an app-server v2 integration test proving `turn/start` with
    `input: []` emits started/completed notifications, sends one Responses
    request, and does not synthesize an empty user message.
    
    ## Validation
    
    - `cargo test -p codex-app-server --test all
    turn_start_with_empty_input_runs_model_request`
  • Defer v1 multi-agent tools behind tool search (#23144)
    Summary: defer v1 multi-agent tools when tool_search and namespace tools
    are available; keep concise searchable descriptions and move the v1
    usage guidance into developer instructions; add targeted coverage.
    Testing: not run per request; ran just fmt.
  • Add body_after_prefix auto-compact token limit scope (#22870)
    ## Why
    
    `model_auto_compact_token_limit` has only been able to budget the full
    active context. That makes it hard to set a small "growth since
    compaction" budget for sessions that preserve a large carried window
    prefix: the preserved prefix can consume the whole budget and force
    immediate repeated compaction.
    
    This PR adds an opt-in `body_after_prefix` scope so callers can apply
    `model_auto_compact_token_limit` to sampled output and later growth
    after the current carried prefix, while still forcing compaction before
    the full model context window is exhausted.
    
    ## What changed
    
    - Adds `AutoCompactTokenLimitScope` with the existing `total` behavior
    as the default and a new `body_after_prefix` mode:
    [`config_types.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/protocol/src/config_types.rs#L24-L37).
    - Threads `model_auto_compact_token_limit_scope` through config loading,
    `Config`, `core-api`, and app-server v2 schema/TypeScript generation.
    - Records the first observed input-token count for a `body_after_prefix`
    compaction window and uses it as the baseline when deciding whether the
    scoped auto-compaction budget is exhausted:
    [`turn.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/core/src/session/turn.rs#L743-L781).
    - Keeps a hard context-window cap in `body_after_prefix`, so scoped
    budgeting cannot let the active context overrun the usable window.
    
    ## Verification
    
    Added compact-suite coverage for the two key behaviors:
    `body_after_prefix` does not re-compact just because the carried prefix
    is larger than the scoped budget, and it still compacts when the total
    active context reaches the configured context window:
    [`compact.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/core/tests/suite/compact.rs#L3003-L3128).
  • Remove ToolsConfig from tool planning (#22835)
    ## Why
    
    `codex-tools` is meant to hold reusable tool primitives, but
    `ToolsConfig` had become a second copy of core runtime decisions instead
    of a small shared contract. It carried provider capabilities, auth/model
    gates, permission and environment state, web/search/image feature gates,
    multi-agent settings, and goal availability from core into `codex-tools`
    ([definition](https://github.com/openai/codex/blob/22dd9ad3929253ed24d7ee4f10f238e95ab25f37/codex-rs/tools/src/tool_config.rs#L97),
    [stored on each
    `TurnContext`](https://github.com/openai/codex/blob/22dd9ad3929253ed24d7ee4f10f238e95ab25f37/codex-rs/core/src/session/turn_context.rs#L87)).
    Every session/context variant then had to build and mutate that snapshot
    before assembling tools.
    
    This PR removes that master object instead of renaming it. Tool planning
    now reads the live `TurnContext`, where `codex-core` already owns those
    decisions, while `codex-tools` keeps only reusable primitives and a
    generic `ToolSetBuilder`/`ToolSet` accumulator.
    
    ## What Changed
    
    - Removed `ToolsConfig` / `ToolsConfigParams` from `codex-tools`; the
    crate keeps the shared helpers that still belong there, including
    request-user-input mode selection, shell backend/type resolution,
    `UnifiedExecShellMode`, and `ToolEnvironmentMode`.
    - Replaced config-snapshot planning with `ToolRouter::from_turn_context`
    and a `spec_plan` pipeline over `CoreToolPlanContext`, deriving provider
    capabilities, auth gates, model support, feature gates, environment
    count, goal support, multi-agent options, web search, and image
    generation from the authoritative turn state.
    - Added generic `codex_tools::ToolSetBuilder` / `ToolSet`, plus the
    small core adapter needed to accumulate `CoreToolRuntime` values and
    hosted model specs.
    - Added the `tool_family::shell` registration module and moved
    shell/unified-exec/memory accounting call sites to read the narrow
    per-turn fields directly.
    - Narrowed `TurnContext` to the remaining explicit per-turn fields
    needed by planning: `available_models`, `unified_exec_shell_mode`, and
    `goal_tools_supported`.
    - Reworked MCP exposure and tool-search setup so deferred/direct MCP
    behavior is driven by the current turn rather than a precomputed config
    snapshot.
    - Replaced the large expected-spec fixture tests with focused
    behavior-level coverage for shell tools, environments, goal and
    agent-job gates, MCP direct/deferred exposure, tool search,
    request-plugin-install, code mode, multi-agent mode, hosted tools, and
    extension executor dispatch.
    
    ## Verification
    
    - `cargo check -p codex-tools`
    - `cargo check -p codex-core --lib`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core spec_plan --lib`
    - `cargo test -p codex-core router --lib`
  • feat: dedicated goal DB (#23300)
    ## Why
    
    Thread goals are moving toward extension-owned runtime behavior, but
    their persisted state was still stored in the shared state database.
    This makes the goal store harder to isolate and keeps future storage
    splits tied to ad hoc runtime plumbing.
    
    This PR gives goals their own SQLite database while keeping the existing
    `StateRuntime` entry point. The goal is to make this the pattern for
    adding more dedicated runtime databases later.
    
    This also reduce load on existing DB and reduce contention
    
    ## Limitation
    Thread preview from goal is not supported anymore. I'm looking into this
    [EDIT]: solved
    
    ## What changed
    
    - Added a dedicated `goals_1.sqlite` database with its own
    `goals_migrations` directory.
    - Moved `thread_goals` creation into the goals DB migration set.
    - Dropped the old `thread_goals` table from the main state DB with a
    normal state migration. There is intentionally no backfill for existing
    goal rows.
    - Changed `GoalStore` to be backed only by the goals DB pool.
    - Removed the old goal-write side effect that filled empty
    `threads.preview` values from the goal objective.
    - Added shared runtime DB path metadata so startup, telemetry, `codex
    doctor`, and repair handling can include future DBs without bespoke path
    lists.
    - Updated Bazel compile data so the new goals migration directory is
    available to `sqlx::migrate!`.
    
    ## Verification
    
    - `cargo check --tests -p codex-state -p codex-cli -p codex-core -p
    codex-app-server`
    - `just fix -p codex-state`
    - `just fix -p codex-cli`
    - `just fix -p codex-app-server`
  • Preserve context baselines for full-history agent forks (#23352)
    ## Why
    
    Full-history agent forks should continue from the same prompt prefix as
    the parent. Dropping the stored `TurnContext` baseline forced the child
    to rebuild startup context on its first turn, which can duplicate
    developer instructions and also loses the cache continuity that a
    full-history fork is supposed to preserve.
    
    Truncated forks are different: once we keep only the last N turns, the
    original prompt prefix is no longer intact, so the child must establish
    a fresh context baseline.
    
    ## What changed
    
    - Preserve `RolloutItem::TurnContext` when forking with
    `SpawnAgentForkMode::FullHistory`, and keep dropping it for truncated
    forks:
    https://github.com/openai/codex/blob/4090717d94c1fc7f33c9bd122be133a0c5752052/codex-rs/core/src/agent/control.rs#L98-L126
    and
    https://github.com/openai/codex/blob/4090717d94c1fc7f33c9bd122be133a0c5752052/codex-rs/core/src/agent/control.rs#L399-L401
    - Remove the special-case MultiAgentV2 usage-hint filtering path.
    Full-history fork now preserves the cached developer prefix instead of
    trying to reconstruct part of it.
    - Extend the fork coverage to assert both sides of the contract:
    full-history forks keep the parent reference baseline, while last-N
    forks rebuild context after truncation:
    https://github.com/openai/codex/blob/4090717d94c1fc7f33c9bd122be133a0c5752052/codex-rs/core/src/agent/control_tests.rs#L603-L759
    and
    https://github.com/openai/codex/blob/4090717d94c1fc7f33c9bd122be133a0c5752052/codex-rs/core/src/agent/control_tests.rs#L854-L977
    
    ## Verification
    
    - `cargo test -p codex-core
    spawn_agent_can_fork_parent_thread_history_with_sanitized_items --
    --nocapture`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core
    spawn_agent_fork_last_n_turns_keeps_only_recent_turns -- --nocapture`
  • Remove explicit connector tool undeferral (#23390)
    ## Summary
    - remove the explicit-connector carveout that kept mentioned app tools
    directly exposed instead of deferred
    - keep the surviving explicit-mention reconstruction only for analytics,
    preserving `codex_app_mentioned` and `codex_app_used.invoke_type`
    - trim the now-unused prompt/tool-exposure plumbing and refresh coverage
    around always-defer behavior
    
    ## Verification
    - `just fmt`
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-core` *(one transient timeout in
    `shell_snapshot::tests::macos_zsh_snapshot_includes_sections`; isolated
    rerun passed)*
    - `cargo test -p codex-core --lib
    shell_snapshot::tests::macos_zsh_snapshot_includes_sections`
    - `cargo test -p codex-core --test all
    explicit_app_mentions_respect_always_defer`
    - `cargo test -p codex-core --lib
    mcp_tool_exposure::tests::always_defer_feature_defers_apps_too`
    - `just fix -p codex-analytics`
    - `just fix -p codex-core`
  • [5 of 7] Replace OverrideTurnContext with ThreadSettings (#22508)
    **Stack position:** [5 of 7]
    
    ## Summary
    
    This PR adds `Op::ThreadSettings`, a queued settings-only update
    mechanism for changing stored thread settings without starting a new
    turn. It also removes the legacy `Op::OverrideTurnContext` in the same
    layer, so reviewers can see the replacement and deletion together.
    
    ## Changes
    
    - Add `Op::ThreadSettings` for settings-only queued updates.
    - Emit `ThreadSettingsApplied` with the effective thread settings
    snapshot after core applies an update.
    - Route settings-only updates through the same submission queue as user
    input.
    - Migrate remaining `OverrideTurnContext` tests and callers to the
    queued `Op::ThreadSettings` path.
    - Delete `Op::OverrideTurnContext` from the core protocol and submission
    loop.
    
    This stack addresses #20656 and #22090.
    
    ## Stack
    
    1. [1 of 7] [Add thread settings to
    UserInput](https://github.com/openai/codex/pull/23080)
    2. [2 of 7] [Remove
    UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
    3. [3 of 7] [Remove
    UserTurn](https://github.com/openai/codex/pull/23075)
    4. [4 of 7] [Placeholder for OverrideTurnContext
    cleanup](https://github.com/openai/codex/pull/23087)
    5. [5 of 7] [Replace OverrideTurnContext with
    ThreadSettings](https://github.com/openai/codex/pull/22508) (this PR)
    6. [6 of 7] [Add app-server thread settings
    API](https://github.com/openai/codex/pull/22509)
    7. [7 of 7] [Sync TUI thread
    settings](https://github.com/openai/codex/pull/22510)
  • [codex] Extract turn skill and plugin injections (#23396)
    ## Why
    
    `run_turn` had accumulated the turn-scoped skill, plugin, app, MCP,
    connector-selection, and analytics setup inline. That made the
    orchestration path harder to scan even though the actual turn item
    injection still needs to stay in `run_turn` so ordering is explicit.
    
    ## What changed
    
    This extracts that setup into `build_skills_and_plugins`, which returns
    the combined injection `ResponseItem`s and the explicitly enabled
    connector IDs. `run_turn` now keeps the required orchestration pieces:
    context update recording, user input handling, connector selection
    merge, and the explicit per-item `record_conversation_items` calls for
    injection items.
    
    The refactor keeps the change LOC-neutral in `core/src/session/turn.rs`
    and preserves the existing response-item based injection path.
    
    ## Validation
    
    - `cargo test -p codex-core collect_explicit_app_ids_from_skill_items`
    - `just fix -p codex-core`
  • [3 of 7] Remove UserTurn (#23075)
    **Stack position:** [3 of 7]
    
    ## Summary
    
    This PR finishes the input-op consolidation by moving the remaining
    `Op::UserTurn` callers onto `Op::UserInput` and deleting `Op::UserTurn`.
    This touches a lot of files, but it is a low-risk mechanical migration.
    
    ## Stack
    
    1. [1 of 7] [Add thread settings to
    UserInput](https://github.com/openai/codex/pull/23080)
    2. [2 of 7] [Remove
    UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
    3. [3 of 7] [Remove
    UserTurn](https://github.com/openai/codex/pull/23075) (this PR)
    4. [4 of 7] [Placeholder for OverrideTurnContext
    cleanup](https://github.com/openai/codex/pull/23087)
    5. [5 of 7] [Replace OverrideTurnContext with
    ThreadSettings](https://github.com/openai/codex/pull/22508)
    6. [6 of 7] [Add app-server thread settings
    API](https://github.com/openai/codex/pull/22509)
    7. [7 of 7] [Sync TUI thread
    settings](https://github.com/openai/codex/pull/22510)
  • [2 of 7] Remove UserInputWithTurnContext (#23081)
    **Stack position:** [2 of 7]
    
    ## Summary
    
    This PR removes the overlapping `Op::UserInputWithTurnContext` variant
    now that `Op::UserInput` can carry thread settings overrides directly.
    
    ## Stack
    
    1. [1 of 7] [Add thread settings to
    UserInput](https://github.com/openai/codex/pull/23080)
    2. [2 of 7] [Remove
    UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
    (this PR)
    3. [3 of 7] [Remove
    UserTurn](https://github.com/openai/codex/pull/23075)
    4. [4 of 7] [Placeholder for OverrideTurnContext
    cleanup](https://github.com/openai/codex/pull/23087)
    5. [5 of 7] [Replace OverrideTurnContext with
    ThreadSettings](https://github.com/openai/codex/pull/22508)
    6. [6 of 7] [Add app-server thread settings
    API](https://github.com/openai/codex/pull/22509)
    7. [7 of 7] [Sync TUI thread
    settings](https://github.com/openai/codex/pull/22510)
  • [1 of 7] Add thread settings to UserInput (#23080)
    **Stack position:** [1 of 7]
    
    ## Summary
    
    The first three PRs in this stack are a cleanup pass before the actual
    thread settings API work.
    
    Today, core has several overlapping "user input" ops: `UserInput`,
    `UserInputWithTurnContext`, and `UserTurn`. They differ mostly in how
    much next-turn state they carry, which makes the later queued thread
    settings update harder to reason about and review.
    
    This PR starts that cleanup by adding the shared
    `ThreadSettingsOverrides` payload and allowing `Op::UserInput` to carry
    it. Existing variants remain in place here, so this layer is mostly a
    behavior-preserving API shape change plus mechanical constructor
    updates.
    
    ## End State After PR3
    
    By the end of PR3, `Op::UserInput` is the only "user input" core op. It
    can carry optional thread settings overrides for callers that need to
    update stored defaults with a turn, while callers without updates use
    empty settings. `Op::UserInputWithTurnContext` and `Op::UserTurn` are
    deleted.
    
    ## End State After PR5
    
    By the end of PR5, core will have only two ops for this area:
    
    - `Op::UserInput` for user-input-bearing submissions.
    - `Op::ThreadSettings` for settings-only updates.
    
    ## Stack
    
    1. [1 of 7] [Add thread settings to
    UserInput](https://github.com/openai/codex/pull/23080) (this PR)
    2. [2 of 7] [Remove
    UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
    3. [3 of 7] [Remove
    UserTurn](https://github.com/openai/codex/pull/23075)
    4. [4 of 7] [Placeholder for OverrideTurnContext
    cleanup](https://github.com/openai/codex/pull/23087)
    5. [5 of 7] [Replace OverrideTurnContext with
    ThreadSettings](https://github.com/openai/codex/pull/22508)
    6. [6 of 7] [Add app-server thread settings
    API](https://github.com/openai/codex/pull/22509)
    7. [7 of 7] [Sync TUI thread
    settings](https://github.com/openai/codex/pull/22510)
  • cleanup: Remove skill env var dependency prompting (#22721)
    Deletes the skill env var dependency prompt feature and its runtime
    path. env_var entries in skill dependency metadata are now silently
    ignored during skill loading.
  • [codex] Remove external websocket session resets (#23384)
    ## Why
    
    Compaction now installs replacement history inside the session, but the
    turn and compaction callers were still reaching into
    `ModelClientSession` to reset websocket transport state after that
    install. That made a transport-level reset part of the compaction API
    even though websocket incremental request selection already checks
    whether the next request is a strict extension of the previous one and
    falls back to a full `response.create` when it is not.
    
    ## What changed
    
    - Removed the compaction-side calls to `reset_websocket_session` from
    `compact.rs` and `session/turn.rs`.
    - Simplified pre-sampling and mid-turn compaction helpers so they return
    `CodexResult<()>` instead of carrying a reset flag.
    - Made `ModelClientSession::reset_websocket_session` private to
    `client.rs`, leaving only the websocket timeout recovery path inside the
    client as a caller.
    
    ## Validation
    
    - `cargo test -p codex-core --test all
    responses_websocket_creates_on_non_prefix`
    - `cargo test -p codex-core --test all
    steered_user_input_waits_for_model_continuation_after_mid_turn_compact`
    - `cargo test -p codex-core --test all
    pre_sampling_compact_runs_on_switch_to_smaller_context_model`
  • [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.
  • [codex] Trim unused TurnContextItem fields (#22709)
    ## Why
    
    `TurnContextItem` is the durable baseline used to reconstruct context
    diffs across resume/fork. Most of the old persisted-only fields on it
    are no longer read, so keeping them in rollout snapshots adds schema
    surface and state that can drift without affecting reconstruction.
    
    `summary` is the exception: older Codex versions require it to
    deserialize `turn_context` records, so keep writing a default
    compatibility value until that schema surface can be removed safely.
    
    ## What changed
    
    - Removed the unused persisted fields from `TurnContextItem`: trace ids,
    user/developer instructions, output schema, and truncation policy.
    - Kept `summary` with a compatibility comment and made
    `TurnContext::to_turn_context_item` write `ReasoningSummary::Auto`
    instead of live turn state.
    - Updated rollout/context reconstruction fixtures for the retained
    summary field.
    
    ## Verification
    
    - `cargo test -p codex-protocol --lib turn_context_item`
    - `cargo test -p codex-rollout
    resume_candidate_matches_cwd_reads_latest_turn_context`
    - `cargo test -p codex-state turn_context`
    - `cargo test -p codex-core --lib
    new_default_turn_captures_current_span_trace_id`
    - `cargo test -p codex-core --lib
    record_initial_history_resumed_turn_context_after_compaction_reestablishes_reference_context_item`
    - `cargo test -p codex-core --test all
    emits_warning_when_resumed_model_differs`
    - `git diff --check`
  • 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`.
  • goal: pause continuation loops on usage limits and blockers (#23094)
    Addresses #22833, #22245, #23067
    
    ## Why
    `/goal` can keep synthesizing turns even when the next turn cannot make
    meaningful progress. Hard usage exhaustion can replay failing turns, and
    repeated permission or external-resource blockers can keep burning
    tokens while waiting for user or system intervention.
    
    ## What changed
    - Add resumable `blocked` and `usageLimited` goal states. As with
    `paused`, goal continuation stops with these states.
    - Move to `usageLimited` after usage-limit failures.
    - Allow the built-in `update_goal` tool to set `blocked` only under
    explicit repeated-impasse guidance. Updated goal continuation prompt to
    specify that agent should use `blocked` only when it has made at least
    three attempts to get past an impasse.
    
    Most of the files touched by this PR are because of the small app server
    protocol update.
    
    ## Validation
    
    I manually reproduced a number of situations where an agent can run into
    a true impasse and verified that it properly enters `blocked` state. I
    then resumed and verified that it once again entered `blocked` state
    several turns later if the impasse still exists.
    
    I also manually reproduced the usage-limit condition by creating a
    simulated responses API endpoint that returns 429 errors with the
    appropriate error message. Verified that the goal runtime properly moves
    the goal into `usageLimited` state and TUI UI updates appropriately.
    Verified that `/goal resume` resumes (and immediately goes back into
    `ussageLImited` state if appropriate).
    
    
    ## Follow-up PRs
    
    Small changes will be needed to the GUI clients to properly handle the
    two new states.
  • Fix remote turn diff display roots (#23261)
    ## Why
    
    `TurnDiffTracker` computes a display root so turn diffs can be rendered
    repo-relative. For remote exec-server turns, the selected turn `cwd` may
    exist only inside the selected environment, but `run_turn` was
    discovering the git root through the local host filesystem. When that
    lookup failed, nested remote-session diffs fell back to the nested `cwd`
    and showed `/tmp/...`-prefixed paths instead of repo-relative paths.
    
    ## What changed
    
    - Resolve the diff display root from the primary selected turn
    environment when one exists, using that environment's filesystem and
    `cwd`.
    - Add `codex_git_utils::get_git_repo_root_with_fs(...)` so git-root
    discovery can run against an `ExecutorFileSystem`, including remote
    environments.
    - Reuse that helper from `resolve_root_git_project_for_trust(...)` and
    add coverage for `.git` gitdir-pointer detection.
    
    ## Validation
    
    - Devbox Bazel: `//codex-rs/core:core-unit-tests
    --test_filter=get_git_repo_root_with_fs_detects_gitdir_pointer`
    - Devbox Docker-backed remote-env repro: `//codex-rs/core:core-all-test
    --test_filter=apply_patch_turn_diff_paths_stay_repo_relative_when_session_cwd_is_nested`
  • chore: make token usage async (#23305)
    Make the `TokenUsageContributor` async. This will be required for future
    extension and it's basically free
  • chore: isolate thread goal storage behind GoalStore (#23295)
    ## Why
    
    Thread goal persistence is being prepared for a dedicated storage
    boundary. Before that split, goal-specific reads, writes, accounting,
    and cleanup were exposed directly on `StateRuntime`, so core and
    app-server callsites stayed coupled to the full runtime instead of a
    goal-specific store.
    
    This PR introduces that boundary without changing the goal wire API or
    current persistence behavior. Callers now go through
    `StateRuntime::thread_goals()` and the new `GoalStore`, while
    `GoalStore` still uses the existing state DB pool underneath.
    
    ## What changed
    
    - Added `GoalStore` in `state/src/runtime/goals.rs` and exposed it from
    `StateRuntime` via `thread_goals()`.
    - Moved thread-goal reads, writes, status updates, pause, delete, and
    usage accounting onto `GoalStore`.
    - Updated core session goal handling, app-server goal RPCs, resume
    snapshots, and goal tests to use the store boundary.
    - Kept thread deletion responsible for cascading goal cleanup by
    deleting the goal through the store only after a thread row is removed.
    
    ## Testing
    
    - Existing goal persistence, resume, and accounting tests were updated
    to exercise the new `GoalStore` access path.
  • 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.
  • Make multi-agent v2 tool namespace configurable (#23147)
    ## Summary
    - Add `features.multi_agent_v2.tool_namespace` with config/schema
    validation for Responses-compatible namespace values.
    - Thread the resolved namespace into `ToolsConfig` for normal turns and
    review turns.
    - Wrap MultiAgentV2 tool specs and registry names in the configured
    namespace when namespace tools are supported, while falling back to the
    plain tool names when they are not.
    
    ## Validation
    - `just fmt`
    - `just write-config-schema`
    - `cargo test -p codex-features multi_agent_v2_feature_config --
    --nocapture`
    - `cargo test -p codex-core test_build_specs_multi_agent_v2 --
    --nocapture`
    - `cargo test -p codex-core multi_agent_v2_config -- --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_rejects_invalid_tool_namespace -- --nocapture`
    - `cargo test -p codex-tools`
    - `git diff --check`
  • test: construct permission profiles directly (#23030)
    ## Why
    
    `SandboxPolicy` is now a legacy compatibility shape, but several tests
    still built a `SandboxPolicy` only to immediately convert it into
    `PermissionProfile` for APIs that already accept canonical runtime
    permissions. Those detours make it harder to audit where legacy sandbox
    policy is still required, because boundary-only usages are mixed
    together with ordinary test setup.
    
    ## What Changed
    
    - Updated tests in `codex-core`, `codex-exec`, `codex-analytics`, and
    `codex-config` to construct `PermissionProfile` values directly when the
    code under test takes a permission profile.
    - Changed exec-policy, request-permissions, session, and sandbox test
    helpers to pass `PermissionProfile` through instead of converting from
    `SandboxPolicy` internally.
    - Left `SandboxPolicy` in place where tests are explicitly exercising
    legacy compatibility or request/response boundaries.
    
    ## Test Plan
    
    - `cargo test -p codex-analytics -p codex-config`
    - `cargo test -p codex-core --lib safety::tests`
    - `cargo test -p codex-core --lib exec_policy::tests::`
    - `cargo test -p codex-core --lib exec::tests`
    - `cargo test -p codex-core --lib guardian_review_session_config`
    - `cargo test -p codex-core --lib tools::network_approval::tests`
    - `cargo test -p codex-core --lib
    tools::runtimes::shell::unix_escalation::tests`
    - `cargo test -p codex-core --lib managed_network`
    - `cargo test -p codex-core --test all request_permissions::`
    - `cargo test -p codex-exec sandbox`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23030).
    * #23036
    * __->__ #23030
  • Improve goal completion usage reporting (#22907)
    ## Why
    
    Goal completion follow-up turns currently receive a preformatted English
    usage sentence such as `time used: 2586 seconds`. That nudges the model
    to echo an awkward raw seconds count in the final reply, even though the
    tool result already exposes structured usage fields like
    `goal.timeUsedSeconds`, `goal.tokensUsed`, and `goal.tokenBudget`.
    
    ## What changed
    
    - Replace the preformatted completion usage sentence with guidance to
    read the structured goal fields from the tool result.
    - Preserve token-budget reporting while allowing the model to phrase
    elapsed time in a concise, human-friendly way that fits the response
    language.
    - Update core coverage for both the generated completion guidance and
    the session flow that forwards it back to the model.
    
    ## Verification
    
    Previously, it would have output a final message indicating that it
    "worked for 303 seconds". Now it shows the following:
    
    <img width="286" height="35" alt="image"
    src="https://github.com/user-attachments/assets/d7011880-9449-46a7-856f-4e50ae00eb45"
    />
  • core: set permission profiles from snapshots (#22920)
    ## Why
    
    #22891 moved the TUI turn-command path to pass `ActivePermissionProfile`
    instead of the full `PermissionProfile`, but the remaining
    config/session bridge still accepted the concrete `PermissionProfile`
    and active profile id as separate arguments. That shape made it too easy
    for future callers to update the concrete profile and active profile id
    out of sync.
    
    This PR makes the trusted session snapshot path pass one coherent value
    into `Permissions`, while keeping `requirements.toml` enforcement owned
    by the existing constrained permission state.
    
    ## What Changed
    
    - Added `PermissionProfileSnapshot` as the public snapshot value for
    trusted session/config synchronization.
    - Changed `Permissions::set_permission_profile_from_session_snapshot()`
    and `replace_permission_profile_from_session_snapshot()` to take a
    `PermissionProfileSnapshot`.
    - Updated the replacement path to derive its constrained
    `PermissionProfile` from the snapshot, so callers cannot pass a separate
    profile that disagrees with the snapshot.
    - Removed the internal tuple-style
    `PermissionProfileState::set_active_permission_profile()` mutation path.
    - Updated core session projection and TUI call sites to construct
    explicit legacy or active snapshots.
    - Documented the snapshot constructors so legacy use and id/profile
    mismatch hazards are called out at the API boundary.
    - Added a focused config test that verifies snapshot updates still
    respect existing permission constraints.
    
    ## How To Review
    
    1. Start with `codex-rs/core/src/config/resolved_permission_profile.rs`;
    `PermissionProfileSnapshot` is the public wrapper, while
    `ResolvedPermissionProfile` stays internal.
    2. Check `codex-rs/core/src/config/mod.rs` to confirm both
    session-snapshot setters validate through `PermissionProfileState` and
    no longer accept loose profile/id pairs.
    3. Skim `codex-rs/core/src/session/session.rs` for the session
    projection path; it now builds the snapshot before installing it.
    4. Skim the TUI changes as call-site migration from loose argument pairs
    to explicit snapshot construction.
    
    ## Verification
    
    - `cargo test -p codex-core
    permission_snapshot_setter_preserves_permission_constraints`
    - `cargo test -p codex-tui status_permissions_`
    - `cargo test -p codex-tui
    session_configured_preserves_profile_workspace_roots`
    - `just fix -p codex-core -p codex-tui`
  • Preserve image detail in app-server inputs (#20693)
    ## Summary
    
    - Add optional image detail to user image inputs across core, app-server
    v2, thread history/event mapping, and the generated app-server
    schemas/types.
    - Preserve requested detail when serializing Responses image inputs:
    omitted detail stays on the existing `high` default, while explicit
    `original` keeps local images on the original-resolution path.
    - Support `high`/`original` consistently for tool image outputs,
    including MCP `codex/imageDetail`, code-mode image helpers, and
    `view_image`.