Commit Graph

3051 Commits

  • Fix extension turn item emitter test event ordering (#24936)
    ## Why
    
    PR #24813 added extension `TurnItemEmitter` coverage and introduced a
    test that records a conversation history item before asserting
    extension-emitted turn item events.
    
    `record_conversation_items()` also emits a `RawResponseItem` event to
    observers. The test was reading from the same event receiver and
    expected the next event to be `ItemStarted`, so the test failed reliably
    once the setup history item was present.
    
    ## What Changed
    
    Update
    `passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call` to
    consume and assert the expected setup `RawResponseItem` before checking
    the extension `ItemStarted`, `WebSearchBegin`, `ItemCompleted`, and
    `WebSearchEnd` events.
    
    This is test-only and does not change extension runtime behavior.
    
    ## Verification
    
    - `cargo nextest run --no-fail-fast -p codex-core
    tools::handlers::extension_tools::tests::passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call`
  • Reap stale multi-agent slots (#24903)
    ## Summary
    
    - Let `close_agent` clean up an agent that is still registered in
    `AgentRegistry` even when its underlying thread is already missing.
    - Preserve the explicit-close boundary: for known stale thread-spawn
    agents, mark the persisted spawn edge `Closed`, then treat
    `ThreadNotFound` / `InternalAgentDied` as a successful close so the
    registry slot can be released.
    - Add a regression for MultiAgentV2 task-name targets where
    `close_agent("worker")` succeeds after the worker thread has already
    disappeared.
    
    ## Motivation
    
    A worker can disappear from `ThreadManager` while its metadata still
    exists in the root `AgentRegistry`. Before this change, the close tool
    failed while trying to subscribe to the missing thread status, so it
    never reached the cleanup path that releases the registered agent slot.
    With `agents.max_threads = 1`, an explicit close of that stale task-name
    agent could fail and leave the session unable to spawn a replacement.
    
    ## Scope
    
    This PR intentionally does not add automatic stale-agent reaping to
    `spawn_agent`, `resume_agent`, or `list_agents`. A thread being missing
    from `ThreadManager` is not the same as an explicit close: persisted
    open spawn edges are still the durable source of truth for resume and
    task-name ownership until `close_agent` is called.
    
    ## Validation
    
    - `just test -p codex-core -E
    'test(multi_agent_v2_close_agent_reaps_stale_task_name_target) |
    test(resume_agent_from_rollout_reopens_open_descendants_after_manager_shutdown)'`
    - `just fix -p codex-core`
  • extension-api: add TurnItemEmitter to tool calls (#24813)
    ## Why
    Extension-contributed tools need to emit visible turn items through
    Codex's normal event and persistence pipeline.
    
    ## What
    - Add `TurnItemEmitter` to extension `ToolCall`s and route the core
    implementation through `Session::emit_turn_item_*`.
    - Hold weak session and turn references so retained tool calls cannot
    keep host state alive.
    - Provide a no-op emitter for extension test callers.
    
    ## Test Plan
    - `just test -p codex-core -E
    'test(passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call)'`
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • 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 thread start contributor facts (#24915)
    Summary: add session source and persistent-state availability to
    ThreadStartInput; populate them from session init; update existing goal
    test harness constructors. Tests: just fmt; git diff --check. No full
    tests or clippy run per request.
  • Add Guardian review metrics (#24897)
    ## Why
    
    Guardian reviews already emit analytics events, but we do not expose
    aggregate OpenTelemetry metrics for review volume, latency, token usage,
    or terminal outcomes. That makes it harder to monitor Guardian behavior
    during rollouts and to compare review outcomes by source, action type,
    session kind, model, and failure mode.
    
    ## What Changed
    
    - Added Guardian review metric names for count, total duration, time to
    first token, and token usage in `codex-rs/otel`.
    - Added `core/src/guardian/metrics.rs` to convert
    `GuardianReviewAnalyticsResult` into sanitized metric tags covering
    decision, terminal status, failure reason, approval request source,
    reviewed action, session kind, risk/outcome, model, reasoning effort,
    and context/truncation state.
    - Emitted the new metrics from `track_guardian_review` for each terminal
    Guardian review result.
    
    ## Testing
    
    - Added
    `guardian_review_metrics_record_counts_durations_and_token_usage`, which
    verifies the emitted count, duration, TTFT, token usage histograms, and
    tag set through the in-memory metrics exporter.
  • [codex] Fix Guardian argument comment lint (#24902)
    ## Summary
    - Add the required `/*parent_thread_id*/` argument comment at the
    Guardian review session test callsite flagged by CI.
    
    ## Validation
    - `just fmt`
    - Not run: clippy/tests, per request; CI will cover them.
  • Thread Guardian cache key through session (#24895)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/session/session.rs. Validation was not run per
    request; this branch is expected to rely on the companion split PRs.
  • Assert Guardian prompt cache key reuse (#24894)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/tests.rs. Validation was not run per request;
    this branch is expected to rely on the companion split PRs.
  • Add Guardian review prompt cache key (#24893)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/review_session.rs. Validation was not run per
    request; this branch is expected to rely on the companion split PRs.
  • Export Guardian prompt cache key helper (#24892)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/mod.rs. Validation was not run per request;
    this branch is expected to rely on the companion split PRs.
  • Stabilize Guardian client cache key handling (#24891)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/client.rs. Validation was not run per request; this
    branch is expected to rely on the companion split PRs.
  • Move memories root setup out of core config (#24758)
    ## Why
    
    Config loading should not create or write-authorize the memories root
    just because memory support exists. Memory startup is the code path that
    actually materializes that tree.
    
    ## What
    
    - Stop creating the memories root during Config load and remove it from
    legacy workspace-write projections.
    - Grant the memories root read access only when the memories feature and
    use_memories are enabled.
    - Create the memories root inside memories startup before seeding
    extension instructions.
    - Update config and startup tests around the ownership boundary.
    
    ## Tests
    
    - just fmt
    - just fix -p codex-core
    - just fix -p codex-memories-write
    - just test -p codex-core
    memory_tool_makes_memories_root_readable_without_creating_or_widening_writes
    workspace_write_includes_configured_writable_root_once_without_memories_root
    permission_profile_override_keeps_memories_root_out_of_legacy_projection
    permissions_profiles_allow_direct_write_roots_outside_workspace_root
    default_permissions_profile_populates_runtime_sandbox_policy
    - just test -p codex-memories-write memories_startup_creates_memory_root
    
    Note: a broader just test -p codex-core run is not clean in this
    sandbox; it hit missing test_stdio_server plus seatbelt, realtime, and
    environment-sensitive failures. The changed config tests above pass.
  • [codex] Remove redundant SQLite dynamic tool storage (#24819)
    ## Why
    
    Dynamic tools are defined at thread start and already stored in rollout
    `SessionMeta`, which restores resumed and forked sessions. Persisting
    the same tools through SQLite creates a second runtime persistence path
    that is unnecessary prework for the explicit namespace refactor.
    
    ## What changed
    
    - Restore missing thread-start dynamic tools directly from rollout
    history, including when SQLite is enabled.
    - Remove SQLite dynamic-tool reads, writes, backfill, and thread
    metadata patch plumbing.
    - Add SQLite-enabled resume integration coverage that verifies a
    rollout-defined dynamic tool is still sent after resume.
    
    ## Compatibility
    
    The existing `thread_dynamic_tools` table is intentionally not dropped
    even though it's now unused. Older Codex binaries are allowed to open
    databases migrated by newer binaries and still reference this table;
    dropping it would break that mixed-version path. See
    [here](https://github.com/openai/codex/blob/main/codex-rs/state/src/migrations.rs#L10-L11).
    
    ## Verification
    
    - `just test -p codex-state -p codex-rollout -p codex-thread-store`
    - `just test -p codex-core --test all
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
  • Update rmcp to 1.7.0 (#24763)
    WIll make it easier to uprev when the new draft spec is supported.
    
    Also updates reqwest where needed for compatibility but doesn't update
    it everywhere since this is already a large diff.
    
    The new version of rmcp handles certain kinds of authentication failures
    differently, this patch includes support for identifying the failing scope
    in a WWW-Authenticate header.
  • fix(linux-sandbox): preserve shell cleanup on interruption (#22729)
    ## Why
    Interrupted `shell_command` calls can race with the outer tool-dispatch
    cancellation path. When that happens, the runtime future may be dropped
    before the spawned process gets a chance to run `SIGTERM` cleanup. For
    bwrapd-backed Linux sandbox commands, that can leave synthetic
    protected-path mount bookkeeping such as `.git/.codex` registrations
    under `/tmp` behind after a TUI interruption.
    
    The relevant cancellation points are the outer dispatch race in
    [`core/src/tools/parallel.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/parallel.rs#L91-L132)
    and the process shutdown logic in
    [`core/src/exec.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/exec.rs#L1367-L1393).
    
    ## What changed
    - Keep `shell_command` dispatch alive long enough for the runtime to
    finish cancellation cleanup instead of immediately returning the
    synthetic aborted response.
    - Fold shell-turn cancellation into the existing `ExecExpiration` path
    in
    [`core/src/tools/runtimes/shell.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/runtimes/shell.rs#L267-L274),
    so cancellation and timeout behavior stay centralized.
    - On cancellation, send `SIGTERM` first, wait briefly for cleanup to
    run, then hard-kill any remaining descendants in the original process
    group.
    - Treat `ESRCH` as an already-gone process-group cleanup case in
    `codex-utils-pty`, which keeps best-effort teardown from surfacing a
    stale-process race as an error.
    
    ## Verification
    - `cargo test -p codex-core cancellation`
    - Added regression coverage for:
      - `shell_tool_cancellation_waits_for_runtime_cleanup`
      - `process_exec_tool_call_cancellation_allows_sigterm_cleanup`
  • chore: enable namespace tools for Bedrock (#24713)
    Client-side namespace tools are now supported by bedrock. Enable
    `namespace_tools` for the Amazon Bedrock provider while continuing to
    disable unsupported hosted tools such as image generation and web
    search.
  • [codex] add compaction metadata to turn headers (#24368)
    ## Summary
    - Add `request_kind` values for foreground turn, startup prewarm,
    compaction, and detached memory model requests.
    - Attach compaction dispatch metadata to local Responses, legacy
    `/v1/responses/compact`, and remote v2 compact requests.
    - Add the existing logical context-window identifier as `window_id` on
    turn-owned model request metadata.
    - Keep identity fields optional for detached memory requests, while
    still emitting `request_kind="memory"` in non-git/no-sandbox workspaces.
    
    ## Root Cause
    `x-codex-turn-metadata` has more than one producer. Foreground turns and
    compaction requests own a real turn and should carry that turn identity.
    Detached memory stage-one requests do not own a foreground turn, so
    absent identity fields are valid rather than missing data. Startup
    websocket prewarm is also a model request, but it has `generate=false`
    and must not be counted as a foreground turn.
    
    `thread_source` or session source identifies where a thread came from
    (for example review, guardian, or another subagent). `request_kind`
    identifies what the current outbound model request is doing (`turn`,
    `prewarm`, `compaction`, or `memory`). A review or guardian thread can
    issue either a normal turn request or a compaction request, so source
    cannot replace request kind.
    
    ## Behavior / Impact
    - Ordinary foreground requests send `request_kind="turn"`, their real
    identity fields, and `window_id="<thread_id>:<window_generation>"`.
    - Startup websocket warmup requests send `request_kind="prewarm"` so
    they are not counted as foreground turns.
    - Compaction requests send `request_kind="compaction"`, their real
    owning turn identity, the existing `window_id`, and
    `compaction.{trigger,reason,implementation,phase,strategy}`.
    - Detached memory stage-one requests send `request_kind="memory"`
    without `session_id`, `thread_id`, `turn_id`, or `window_id`; when no
    workspace metadata exists, the kind-only header is still emitted.
    - `session_id`, `thread_id`, `turn_id`, and `window_id` remain optional
    in the header schema because detached memory requests do not own a
    foreground turn or context window.
    - `window_id` is not a new ID system: it is copied from the already-sent
    `x-codex-window-id` / WS client metadata value at model-request dispatch
    time.
    - Existing `x-codex-window-id` HTTP/WS emission, value format,
    generation advancement, resume behavior, and fork reset behavior are
    unchanged.
    - `request_kind`, `window_id`, and upstream turn-owned identity fields
    remain schema-owned; input `responsesapi_client_metadata` cannot replace
    their canonical values.
    - No table, DAG, export, app-server API, or MCP `_meta` schema changes
    are included.
    
    A compaction attempt stopped by a pre-compact hook issues no model
    request and therefore has no request header; its outcome remains in
    analytics events. Status, error, duration, and token deltas also remain
    analytics fields rather than request-header fields.
    
    Future detached-memory attribution using a real initiating turn ID as
    `trigger_turn_id` is intentionally not part of this PR.
    
    ## Sync With Main
    - Final pushed head `716342e79` is rebased onto `origin/main@0d37db4b2`.
    - The metadata conflict came from upstream `#24160`, which added
    `forked_from_thread_id` on the same `turn_metadata` surface. Resolution
    preserves that field and its protection from client metadata override
    alongside this PR's request-kind, compaction, and window-id fields.
    - While resolving the overlapping commits, I removed an accidental
    recursive model-request overlay and a duplicate detached-memory header
    builder before completing the rebase.
    
    ## Latency / User Experience Boundary
    - Foreground turns perform no new filesystem, git, or network work. New
    fields are inserted into metadata already serialized for outgoing
    requests.
    - Compaction issues the same model/HTTP requests with the same prompt,
    model, service tier, and sampling settings; only metadata bytes change.
    - Startup prewarm already sent metadata; it is now correctly classified
    as `prewarm`.
    - Non-git detached memory now sends a small kind-only metadata header
    rather than no header.
    - This client diff adds no user-visible latency mechanism beyond
    negligible serialization and header bytes on already-existing requests.
    
    ## Validation
    On conflict-resolved head `1d35c2cfb` based on `origin/main@487521733`:
    - `just fmt` (passed)
    - `just fix -p codex-core` (passed)
    - `git diff --check origin/main...HEAD` (passed)
    - `just test -p codex-core -E 'test(turn_metadata) |
    test(websocket_first_turn_uses_startup_prewarm_and_create) |
    test(responses_stream_includes_turn_metadata_header_for_git_workspace_e2e)
    |
    test(responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create)
    | test(remote_compact_v2_retries_failures_with_stream_retry_budget) |
    test(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork)'`
    (`23 passed`; `bench-smoke` passed)
    - `just test -p codex-app-server -E
    'test(turn_start_forwards_client_metadata_to_responses_request_v2) |
    test(turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2)
    | test(auto_compaction_remote_emits_started_and_completed_items)'` (`3
    passed`; `bench-smoke` passed)
    - `just test -p codex-memories-write` (`29 passed`; `bench-smoke`
    passed)
  • Drop startup context when truncating forked rollouts (#24751)
    ## Summary
    - Change last-`n` fork truncation to start at the first fork-turn
    boundary instead of returning the full rollout when the fork history is
    shorter than the requested window.
    - Add coverage for the startup-prefix case in both rollout truncation
    tests and agent control spawn behavior.
    - Ensure bounded forked children still rebuild context after the cached
    prefix is truncated.
    
    ## Testing
    - Added unit coverage for truncation behavior when the parent history is
    under the requested fork-turn limit.
    - Added an agent control test covering bounded fork spawn behavior with
    startup context present.
    - Not run (not requested).
  • fix(auto-review) skip legacy notify for auto review threads (#24714)
    ## Summary
    Clear inherited legacy `notify` from Guardian review session config,
    since we should not be passing auto review threads into `notify`
    targets. Keeps legacy notify payload and hook runtime behavior unchanged
    for normal user turns.
    
    ## Testing
    - [x] add a Guardian config regression and dedicated Guardian
    integration test so review sessions cannot inherit parent notify hooks
  • Uprev Rust toolchain pins to 1.95.0 (#24684)
    ## Summary
    - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
    Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
    environment config.
    - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
    match the new version.
    - Leave purpose-specific toolchains unchanged, including the
    `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
    build pin.
    - Includes fixes for new lints from `just fix` and a few codex-authored
    fixes for lints without a suggestion.
  • fix(core): instrument stalled tool-listing handoff (#24667)
    ## Why
    
    When a turn needs a follow-up request after tool output is recorded,
    Codex can still appear stuck in `Thinking` before the next `/responses`
    request is opened. The existing local trace showed the last completed
    response and the absence of a new backend request, but it did not show
    whether the stall was in tool-router preparation or later request setup.
    
    Issue: N/A (internal incident investigation)
    
    ## What Changed
    
    Added trace spans around the pre-stream tool-router handoff in
    `core/src/session/turn.rs`, including the `built_tools` phase and the
    MCP manager read lock.
    
    Added per-server MCP tool-listing spans and trace breadcrumbs in
    `codex-mcp/src/connection_manager.rs` with startup snapshot /
    startup-complete state so a pending MCP client is visible in feedback
    logs instead of looking like a silent hang.
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-mcp`
    - `just test -p codex-core` (prior full rerun fails in this workspace on
    unrelated integration tests: code-mode output length expectations, one
    shell timeout formatting assertion, and shell snapshot timeouts; latest
    review-fix rerun compiled and passed 1160 tests before I stopped the
    abnormally slow unrelated suite)
  • [codex] Remove obsolete goal continuation turn marker (#24658)
    ## Why
    
    `continuation_turn_id` was introduced to distinguish synthetic goal
    continuation turns for the no-tool continuation suppression heuristic.
    #20523 removed that heuristic, but left the marker behind. It is still
    written and cleared without affecting any runtime decision.
    
    ## What Changed
    
    - Remove `GoalRuntimeState::continuation_turn_id`.
    - Remove the marker setter/clearer and their now-no-op start, finish,
    and abort call sites.
    
    ## Testing
    
    - Not run yet (deferred at request).
  • [codex-analytics] add grouped session id to runtime events (#24655)
    ## Why
    - Runtime analytics events report `thread_id`, which identifies the
    individual thread emitting an event
    - They don't report `session_id`, which identifies the shared session
    for a root thread and its subagent threads
    - Emitting both identifiers allows analytics to group related activity
    
    ## What Changed
    - Adds `session_id` to relevant analytics events (thread_initalized,
    turn, turn_steer, compaction, guardian_review)
    - Tracks each thread's session ID in the analytics reducer so subsequent
    thread scoped events emit the same value
    - Carries the shared session ID through subagent initialization
    
    ## Verification
    - `just test -p codex-analytics` validates event payloads and subagent
    session grouping.
    - Focused `codex-app-server` tests validate session IDs for thread,
    turn, and steer events.
    - Focused `codex-core` tests validate root and subagent session ID
    propagation.
  • Restore legacy image detail values (#24644)
    ## Why
    
    Older persisted rollouts can contain `input_image.detail` values of
    `auto` or `low` from before `ImageDetail` was narrowed to
    `high`/`original`. Current deserialization rejects those values, which
    can make resume skip later compacted checkpoints and reconstruct an
    oversized raw suffix before the next compaction attempt.
    
    Confirmed Sentry reports fixed by this compatibility path:
    
    - [CODEX-1H3F](https://openai.sentry.io/issues/7500642496/)
    - [CODEX-1H6N](https://openai.sentry.io/issues/7501025347/)
    - [CODEX-1JDP](https://openai.sentry.io/issues/7504549065/)
    - [CODEX-1HW6](https://openai.sentry.io/issues/7503407986/)
    
    ## Background
    
    [openai/codex#20693](https://github.com/openai/codex/pull/20693) added
    image-detail plumbing for app-server `UserInput` so input images could
    explicitly request `detail: original`. The Slack discussion behind that
    PR was about ScreenSpot / bridge evals where user input images were
    resized, while tool output images already had MCP/code-mode ways to
    request image detail.
    
    In review, the intended new API surface was narrowed to `high` and
    `original`: default to `high`, allow `original` when callers need
    unchanged image handling, and avoid encouraging new `auto` or `low`
    usage. That policy still makes sense for newly emitted values.
    
    The missing compatibility piece is persisted history. Older rollouts can
    already contain `auto` and `low`, and resume reconstructs typed history
    by deserializing those rollout records. Rejecting old values at that
    boundary causes valid compacted checkpoints to be skipped. This PR
    restores `auto` and `low` as real variants so old records deserialize
    and round-trip without being rewritten as `high`, while product paths
    can continue to default to `high` and avoid emitting `auto` for new
    behavior.
    
    ## What changed
    
    - Restored `ImageDetail::Auto` and `ImageDetail::Low` as first-class
    protocol values.
    - Preserved `auto`/`low` through rollout deserialization, MCP image
    metadata, code-mode image output, and schema/type generation.
    - Kept local image byte handling conservative: only `original` switches
    to original-resolution loading; `auto`/`low`/`high` continue through the
    resize-to-fit path while retaining their detail value.
    - Added regression coverage for enum round-tripping and code-mode `low`
    detail handling.
    
    ## Testing
    
    - `just write-app-server-schema`
    - `just test -p codex-protocol`
    - `just test -p codex-tools`
    - `just test -p codex-code-mode`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core
    suite::rmcp_client::stdio_image_responses_preserve_original_detail_metadata`
    - `just test -p codex-core
    suite::code_mode::code_mode_can_use_mcp_image_result_with_image_helper`
    - Loaded broken rollouts on local fixed builds, and started/completed
    new turns.
    
    I also attempted `just test -p codex-core`; the local broad run did not
    finish green: 2559 tests run, 2467 passed, 55 flaky, 91 failed, 1 timed
    out. The failures were broad timeout/deadline failures across unrelated
    areas; targeted changed-path core tests above passed.
  • windows-sandbox: remove SandboxPolicy runner plumbing (#23813)
    ## Why
    
    The Windows sandbox runner still carried the old `SandboxPolicy`
    compatibility path even though core now computes `PermissionProfile`.
    That meant Windows command-runner execution could only see the legacy
    projection, so profile-only filesystem rules such as deny globs were not
    part of the runner input.
    
    ## What Changed
    
    - Removed the Windows-local `SandboxPolicy` parser/export and deleted
    `windows-sandbox-rs/src/policy.rs`.
    - Changed restricted-token capture/session setup, elevated setup,
    world-writable audit, read-root grant, and command-runner session APIs
    to accept `PermissionProfile` plus the profile cwd.
    - Bumped the elevated command-runner IPC protocol to version 2 because
    `SpawnRequest` now carries `permission_profile` /
    `permission_profile_cwd` instead of the legacy `policy_json_or_preset` /
    `sandbox_policy_cwd` fields.
    - Updated core exec, unified exec, debug-sandbox, TUI setup/grant flows,
    and app-server setup to pass the actual effective `PermissionProfile`.
    - Left regression coverage asserting the old IPC policy fields are
    absent and the runner serializes tagged `PermissionProfile` JSON.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-app-server
    request_processors::windows_sandbox_processor`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-app-server
    -p codex-cli -p codex-tui`
    - `just fix -p codex-cli -p codex-tui`
    - `just fix -p codex-windows-sandbox -p codex-tui`
    - `rg "\\bSandboxPolicy\\b" codex-rs/windows-sandbox-rs` returned no
    matches.
    
    Note: `cargo test -p codex-cli` was attempted but did not reach crate
    tests because local disk filled while compiling dependencies (`No space
    left on device`). The targeted clippy pass compiled the affected CLI/TUI
    surfaces afterward.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23813).
    * #24108
    * __->__ #23813
  • Add forked_from_thread_id turn metadata (#24160)
    ## Why
    
    When Codex calls responsesapi, we currently send `session_id`,
    `thread_id`, and `turn_id` among other things as
    `client_metadata["x-codex-turn-metadata"]`. This PR adds
    `forked_from_thread_id` which helps explain the "lineage" of a forked
    thread.
    
    ## What's changed
    
    - Track the immediate history source copied into a forked thread through
    thread/session creation, including subagent and review turn metadata
    paths.
    - Include `forked_from_thread_id` in Codex turn metadata while
    preventing turn-scoped Responses API client metadata from overwriting
    Codex-owned lineage fields.
    - Add coverage for fork lineage in turn metadata and the app-server
    Responses API request path.
  • 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.
  • standalone websearch extension (#23823)
    ## Summary
    
    Add the extension-backed standalone `web.run` tool so Codex can call the
    standalone search endpoint through the `codex-api` search client and
    return its encrypted output to Responses.
    
    - gate the new tool behind `standalone_web_search`
    - install the extension in the app-server thread registry and hide
    hosted `web_search` when standalone search is enabled for OpenAI
    providers so the two paths stay mutually exclusive
    - build search context from persisted history using a small tail
    heuristic: previous user message, assistant text between the last two
    user turns capped at about 1k tokens, and current user message
    
    ## Test Plan
    
    - `cargo test -p codex-web-search-extension`
    - `cargo test -p codex-api`
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates`
  • Move memory state to a dedicated SQLite DB (#24591)
    ## Summary
    
    Generated memory rows and their stage-one/stage-two job state currently
    live in `state_5.sqlite` alongside thread metadata. That makes memory
    cleanup and regeneration share the main state schema even though those
    rows are memory-pipeline data and can be rebuilt independently from the
    durable thread records.
    
    This PR moves the memory-owned tables into a dedicated
    `memories_1.sqlite` runtime database while keeping thread metadata in
    `state_5.sqlite`.
    
    ## Changes
    
    - Adds a separate memories DB runtime, migrator, path helpers, telemetry
    kind, and Bazel compile data for `state/memory_migrations`.
    - Introduces `MemoryStore` behind `StateRuntime::memories()` and moves
    memory table/job operations onto that store.
    - Drops the old memory tables from the state DB and recreates their
    schema in `state/memory_migrations/0001_memories.sql`.
    - Updates memory startup, citation usage tracking, rollout pollution
    handling, `debug clear-memories`, and app-server `memory/reset` to
    operate through the memories DB.
    - Preserves cross-DB behavior by hydrating thread metadata from the
    state DB when selecting visible memory outputs and checking stage-one
    staleness.
    
    ## Verification
    
    - Added/updated `codex-state` tests for deleted-thread memory visibility
    and already-polluted phase-two enqueue behavior.
    - Updated `debug clear-memories`, app-server `memory/reset`, and
    memories startup tests to seed and assert memory rows through
    `memories_1.sqlite`.
  • 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
  • tui: add named permission profile picker (#21559)
    ## Why
    
    Users who opt into named permission profiles through
    `default_permissions` or `[permissions.*]` should stay in named-profile
    semantics when they open `/permissions`. The legacy picker rewrites
    those users into anonymous preset state, which loses the active profile
    identity and hides custom configured profiles.
    
    ## What changed
    
    - Switch `/permissions` to a profile-aware picker when profile mode is
    active.
    - Show friendly built-in labels instead of raw `:` profile syntax.
    - Include configured custom profiles and their descriptions in the
    picker.
    - Route selections through the split TUI profile-selection flow below
    this PR.
    - Add TUI snapshots and regression coverage for built-ins, custom
    profiles, and conflicting legacy runtime overrides.
    
    ## Stack
    
    1. [#22931](https://github.com/openai/codex/pull/22931):
    runtime/session/network propagation for active permission profiles.
    2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
    plumbing and guardrail flow.
    3. **This PR**: profile-aware `/permissions` menu and custom profile
    display.
    
    ## UX impact
    
    In profile mode, `/permissions` shows the same human-facing built-ins
    users already know:
    
    ```text
    Default
    Auto-review
    Full Access
    Read Only
    locked-down
    web-enabled
    ```
    
    Selecting `locked-down` keeps `active_permission_profile =
    Some("locked-down")`; selecting a built-in keeps the friendly label
    while switching to its named built-in profile.
    
    ## Screenshots
    
    Live `$test-tui` smoke screenshots uploaded through GitHub attachments:
    
    **Profile mode with built-ins and custom profiles**
    
    <img width="832" alt="Profile mode permissions picker with custom
    profiles"
    src="https://github.com/user-attachments/assets/58b72431-418c-4839-9e39-575076db4c8f"
    />
    
    **Legacy mode remains anonymous preset picker**
    
    <img width="1232" alt="Legacy permissions picker"
    src="https://github.com/user-attachments/assets/95f413ab-4cee-411c-9afb-92580a885c97"
    />
    
    <img width="1296" height="906" alt="image"
    src="https://github.com/user-attachments/assets/ea381a78-9904-4aa2-828f-b7f2e43f60f2"
    />
    
    <img width="705" height="207" alt="Screenshot 2026-05-18 at 2 58 00 PM"
    src="https://github.com/user-attachments/assets/2fa6dd71-0296-449e-a6de-a72d78a1cb70"
    />
    
    ## Validation
    
    - `git diff --cached --check` before commit.
    - Full test run skipped at the user request while pushing the split
    stack.
  • feat: gate dedicated memories tools in config (#24600)
    ## Why
    
    The memories extension already has dedicated `list`, `read`, `search`,
    and `add_ad_hoc_note` tools, but app-server registration was still
    disabled. The memories app collaborator needs an explicit config switch
    so those native extension tools can be exposed intentionally, without
    making ordinary memory prompt usage automatically register the dedicated
    tool surface.
    
    ## What changed
    
    - Added `[memories].dedicated_tools`, defaulting to `false`, to
    `MemoriesToml` / `MemoriesConfig`.
    - Regenerated `core/config.schema.json` for the new setting.
    - Registered the memories extension as a `ToolContributor`, while
    keeping tool contribution gated on both memories being enabled and
    `dedicated_tools = true`.
    - Added tests for the disabled default, the enabled dedicated-tools
    path, and installer registration.
    
    ## Verification
    
    - `just test -p codex-config -p codex-memories-extension`
  • Move MCP tool naming mode into manager (#21576)
    ## Why
    
    The `non_prefixed_mcp_tool_names` feature should be applied where MCP
    tools become model-visible, not by remapping names later in core.
    Keeping the decision in `McpConnectionManager` construction makes
    `ToolInfo` the single shaped view that spec building, deferred tool
    search, routing, and unavailable-tool placeholders can consume directly.
    
    This also preserves the existing external behavior while the feature is
    off, and keeps the feature-on behavior for code mode and hooks explicit
    at the manager boundary.
    
    ## What Changed
    
    - Add `McpToolNameMode` to `codex-mcp` and flow it through `McpConfig`
    into `McpConnectionManager::new`.
    - Normalize MCP `ToolInfo` names in the manager using either
    legacy-prefixed namespaces or non-prefixed namespaces; the legacy path
    adds `mcp__` without restoring the old trailing namespace suffix.
    - Remove the core-side MCP name remapping path so specs, tool search,
    session resolution, and unavailable-tool placeholder construction use
    the manager-provided `ToolName` values directly.
    - Keep code mode flattening on the `__` namespace separator.
    - Preserve hook compatibility by giving non-prefixed MCP hook names
    legacy `mcp__...` matcher aliases.
    - Add/adjust integration and unit coverage for non-prefixed code-mode
    behavior, hook matching with the feature on and off, and manager-level
    legacy prefixing.
    
    ## Testing
    
    - `cargo test -p codex-mcp --lib`
    - `cargo test -p codex-core --lib tools::spec::tests -- --nocapture`
    - `cargo test -p codex-core --lib mcp_tools -- --nocapture`
    - `cargo test -p codex-core --lib mcp_tool_exposure -- --nocapture`
    - `cargo test -p codex-core --test all mcp_tool -- --nocapture`
    - `cargo test -p codex-core --test all search_tool -- --nocapture`
    - `cargo test -p codex-core --test all hooks_mcp -- --nocapture`
    - `cargo test -p codex-core --test all
    code_mode_uses_non_prefixed_mcp_tool_names_when_feature_enabled --
    --nocapture`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-features`
  • [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`
  • centralize Responses retry policy (#24131)
    ## Why
    
    #23951 added remote compaction v2 retries, but it left the retry and WS
    -> HTTPS fallback behavior duplicated between normal Responses turns and
    compaction. This follow-up centralizes the common retry handling so
    future changes to fallback, retry delay, retry notifications, and retry
    sleep do not have to be kept in sync across both callsites.
    
    ## What changed
    
    - Added `core/src/responses_retry.rs` with a shared handler for
    retryable Responses stream errors.
    - Reused that handler from normal turn sampling and remote compaction
    v2.
    - Kept each callsite responsible for its retry budget: normal turns
    still use `stream_max_retries`, while compaction v2 still uses
    `min(stream_max_retries, 2)`.
    - Preserved caller-specific behavior around non-retryable errors,
    context-window errors, usage-limit errors, and compact-specific final
    failure logging.
    
    The shared handler now owns:
    
    - WS -> HTTPS fallback warning emission
    - retry delay selection, including server-requested stream retry delay
    - retry logging
    - first-WebSocket-retry notification suppression
    - `Reconnecting... n/max` stream-error notification
    - sleeping before the next retry attempt
    
    ## Verification
    
    - `cargo test -p codex-core remote_compact_v2`
    - `cargo test -p codex-core websocket_fallback`
    - `just fix -p codex-core`
    
    Did not run the full workspace test suite.
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • chore: stop consuming legacy config profiles (#24076)
    ## Why
    
    The old config-profile mechanism should no longer influence runtime
    behavior now that profile selection has moved to file-based `--profile`
    config files. Core already rejects a selected legacy `profile = "..."`
    with a migration error in
    [`core/src/config/mod.rs`](https://github.com/openai/codex/blob/d6451fcb79edc4a71bc9e811bcda06fd3c36562e/codex-rs/core/src/config/mod.rs#L2521-L2529),
    but a few residual consumers still read legacy `[profiles.*]` data while
    performing managed-feature checks and personality migration.
    
    That kept dead legacy profile state relevant after selection had been
    removed, and could make personality migration depend on a stale or
    missing old profile.
    
    ## What changed
    
    - Stop scanning legacy `[profiles.*]` feature settings when validating
    managed feature requirements.
    - Make personality migration consider only top-level `personality` and
    `model_provider` settings.
    - Remove the now-unused `ConfigToml::get_config_profile` helper.
    - Update personality migration coverage to verify that legacy profile
    personality fields and missing legacy profile names no longer affect
    that migration path.
    
    This keeps the legacy `profile` / `profiles` config shape available for
    the remaining compatibility and migration diagnostics; it only removes
    these behavior consumers.
    
    ## Verification
    
    - Updated `core/tests/suite/personality_migration.rs` for the new
    legacy-profile behavior.
    - Focused test command: `cargo test -p codex-core
    personality_migration`.
  • code-mode: merge stored values by key (#24159)
    ## Summary
    
    Change code-mode stored value updates to merge writes by key instead of
    replacing the session's complete stored-value map after each cell
    completes.
    
    Previously, each cell received a snapshot of stored values and returned
    the complete resulting map. When multiple cells ran concurrently, a
    later completion could overwrite values written by another cell because
    it committed an older snapshot.
    
    This change moves stored-value ownership into `CodeModeService`:
    
    - Each runtime starts from the service's current stored values.
    - Runtime completion reports only keys written by that cell.
    - The service merges those writes into the current stored-value map on
    successful completion.
    - Core no longer replaces its stored-value state from a cell result.
    
    As a result, concurrently executing cells can update different stored
    keys without clobbering one another.
    
    The move into CodeModeService is motivated by a desire to have this
    lifetime tied to a new lifetime object on that side in a subsequent PR.
  • Default function tools into tool hooks (#23757)
    # Why
    
    `PreToolUse`, `PostToolUse`, and `updatedInput` coverage for local
    function tools currently depends on each handler remembering to wire up
    the hook contract itself. That makes coverage easy to miss as new
    function tools are added, even though most of them share the same basic
    shape: a model-facing function call with JSON arguments.
    
    # What
    
    This makes `CoreToolRuntime` provide the default hook contract for
    ordinary local function tools:
    
    - build generic `PreToolUse` and `PostToolUse` payloads from the
    function tool name and arguments
    - apply `updatedInput` rewrites back into function-tool arguments
    through the same default path
    - let tool outputs override the post-hook input or response when they
    have a more stable hook-facing contract
    
    The exceptions stay explicit:
    
    - hosted tools remain outside the generic local function path
    - code-mode `wait` and `write_stdin` opt out for now
    - `PostToolUse` feedback replaces only the model-visible response, so
    code mode keeps its typed tool result
    
    With the generic path in place, the MCP and extension-tool adapters no
    longer need their own duplicate pre/post hook plumbing. The new coverage
    exercises the registry default plus end-to-end local function behavior
    for pre-hook blocking, `updatedInput` rewriting, and post-hook context.
  • package: include zsh fork in Codex package (#23756)
    ## Why
    
    The package layout gives Codex a stable place for runtime helpers that
    should travel with the entrypoint. `shell_zsh_fork` still required users
    to configure `zsh_path` manually, even though we already publish
    prebuilt zsh fork artifacts.
    
    This PR builds on #24129 and uses the shared DotSlash artifact fetcher
    to include the zsh fork in Codex packages when a matching target
    artifact exists. Packaged Codex builds can then discover the bundled
    fork automatically; the user/profile `zsh_path` override is removed so
    the feature uses the package-managed artifact instead of a legacy path
    knob.
    
    ## What Changed
    
    - Added `scripts/codex_package/codex-zsh`, a checked-in DotSlash
    manifest for the current macOS arm64 and Linux zsh fork artifacts.
    - Taught `scripts/build_codex_package.py` to fetch the matching zsh fork
    artifact and install it at `codex-resources/zsh/bin/zsh` when available
    for the selected target.
    - Added package layout validation for the optional bundled zsh resource.
    - Added `InstallContext::bundled_zsh_path()` and
    `InstallContext::bundled_zsh_bin_dir()` for package-layout resource
    discovery.
    - Threaded the packaged zsh path through config loading as the runtime
    `zsh_path` for packaged installs, and removed the config/profile/CLI
    override path.
    - Kept the packaged default zsh override typed as `AbsolutePathBuf`
    until the existing runtime `Config::zsh_path` boundary.
    - Updated app-server zsh-fork integration tests to spawn
    `codex-app-server` from a temporary package layout with
    `codex-resources/zsh/bin/zsh`, matching the new packaged discovery path
    instead of setting `zsh_path` in config.
    - Switched package executable copying from metadata-preserving `copy2()`
    to `copyfile()` plus explicit executable bits, which avoids macOS
    file-flag failures when local smoke tests use system binaries as inputs.
    
    ## Testing
    
    To verify that the `zsh` executable from the Codex package is picked up
    correctly, first I ran:
    
    ```shell
    ./scripts/build_codex_package.py
    ```
    
    which created:
    
    ```
    /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/
    ```
    
    so then I ran:
    
    ```
    /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/bin/codex exec --enable shell_zsh_fork 'run `echo $0`'
    ```
    
    which reported the following, as expected:
    
    ```
    /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/codex-resources/zsh/bin/zsh
    ```
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23756).
    * #23768
    * __->__ #23756
  • [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)'`
  • [codex-analytics] split compaction v2 analytics implementation (#24146)
    ## What changed
    
    - Add a distinct `responses_compaction_v2` value for
    `CodexCompactionEvent.implementation`.
    - Emit that value from the remote compaction v2 path.
    - Keep local compaction as `responses` and legacy `/responses/compact`
    as `responses_compact`.
    
    ## Why
    
    Remote compaction v2 and local prompt-based compaction were both
    reported as `responses`, which made the analytics table collapse two
    different compaction mechanisms into one implementation bucket.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-analytics`
    
    `just test -p codex-core` was started locally, but this PR is
    intentionally being pushed for CI to finish the remaining validation.
  • 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`
  • Add new enterprise requirement gate (#23736)
    Add new enterprise requirement gate.
    
    Validation:
    - `cargo test -p codex-config --lib`
    - `cargo test -p codex-app-server-protocol --lib`
    - `cargo test -p codex-tui --lib debug_config`
    - `cargo test -p codex-app-server --lib` *(fails: stack overflow in
    `in_process::tests::in_process_start_initializes_and_handles_typed_v2_request`;
    reproduces when run alone)*
  • retry remote compaction v2 requests (#23951)
    ## Why
    
    Remote compaction v2 sends a normal `/responses` request with a
    compaction trigger. It should follow the retry semantics used by normal
    Responses streaming calls for transient stream/request failures, while
    keeping a smaller per-transport retry budget because compact attempts
    can run much longer than normal turns.
    
    ## What changed
    
    - Add a v2 compaction retry loop that uses `stream_max_retries`,
    matching normal Responses turn retry mechanics.
    - Cap the compact v2 retry budget at 2 retries per transport with
    `min(stream_max_retries, 2)`.
    - Retry retryable request-open and post-open stream collection failures
    through the same loop.
    - Use the existing 200ms exponential backoff and requested retry delay
    handling used by normal turn retries.
    - Emit the same `Reconnecting... n/max` stream-error notification
    pattern.
    - Fall back from WebSockets to HTTPS after the compact v2 stream retry
    budget is exhausted, then reset the retry counter for HTTPS.
    - Keep final remote-compaction failure logging after retries/fallback
    are exhausted.
    - Treat compact stream EOF before `response.completed` as a retryable
    stream failure.
    - Add compact v2 regression coverage with `request_max_retries = 0` and
    `stream_max_retries = 2`, covering both request-open failure and
    opened-stream EOF in one end-to-end test.
    
    ## Tests
    
    - `just fmt`
    - `cargo test -p codex-core remote_compact_v2`
    - `just fix -p codex-core`