Commit Graph

3836 Commits

  • [codex] surface rollout budget exhaustion (#29715)
    ## Summary
    - surface shared rollout-budget exhaustion as
    `CodexErr::RolloutBudgetExceeded` instead of a generic interrupted turn
    - map it through the existing `CodexErrorInfo` and app-server v2
    `codexErrorInfo` path
    - keep local compaction from retrying after the shared rollout budget is
    exhausted
    
    This gives app-server clients a stable `rolloutBudgetExceeded` error
    they can classify without guessing from `status="interrupted"`.
    
    ## Tests
    - `just test -p codex-core rollout_budget`
  • Make selected plugin roots URI-native (#28918)
    ## Why
    
    Selected capability roots belong to the executor filesystem, not the
    app-server host. Converting their path strings into the host's native
    `Path` breaks whenever the two machines use different path conventions,
    such as a Windows executor behind a Unix app-server.
    
    This PR establishes `PathUri` as the selected-plugin boundary so the
    executor remains authoritative for its paths.
    
    ## What changed
    
    - Require `selectedCapabilityRoots[].location.path` to be a canonical
    `file:` URI and deserialize it directly as `PathUri`; native path
    strings are rejected.
    - Update the app-server schema, generated TypeScript, examples, and
    request coverage for the URI contract.
    - Keep selected roots, resolved plugin locations, manifest paths, and
    manifest resources as `PathUri`.
    - Inspect and read plugin roots and manifests only through the selected
    environment's `ExecutorFileSystem`.
    - Parse executor manifests with the shared URI-native parser from #29620
    instead of projecting them onto the host filesystem.
    - Enforce resource containment lexically and preserve the root URI's
    POSIX or Windows path convention.
    - Cover foreign Windows plugin roots and URI-native manifest resources.
    
    ```text
    thread/start
      selectedCapabilityRoots[].location.path = "file:///C:/plugins/demo"
                                  | PathUri
                                  v
                        ExecutorFileSystem
                                  |
                                  +--> plugin.json
                                  +--> manifest resources
    ```
    
    This PR stops at the shared selected-plugin representation. The next two
    PRs remove the remaining host-path projections in the skill and MCP
    consumers.
    
    ## Stack
    
    1. #29614 — add lexical `PathUri` containment.
    2. #29620 — share URI-native manifest path resolution.
    3. **This PR** — keep selected plugin roots and resources URI-native.
    4. #29626 — load executor skills without host path conversion.
    5. #29628 — resolve executor MCP working directories without host path
    conversion.
  • core: persist initial context window metadata (#29519)
    ## Why
    
    PR #29494 made context-window IDs visible to the model by wrapping the
    token-budget window payload in `<context_window>`, but rollout JSONL
    consumers still could not see the initial window identity by tailing the
    session file. Compacted rollout items carry window IDs only after
    compaction has happened, so a session with no compaction had no durable
    JSONL record for window 0.
    
    This change gives tailing consumers a stable initial-window record at
    session creation time.
    
    ## What Changed
    
    - Added `session_meta.context_window.window_id` for the initial
    context-window identity.
    - `CreateThreadParams` now requires `initial_window_id: String`, so
    thread-store callers cannot accidentally create new threads without
    window-0 metadata.
    - Live thread creation derives the persisted initial window ID from the
    same `AutoCompactWindowIds` used to initialize `SessionState`, keeping
    runtime state and JSONL metadata aligned.
    - Rollout reconstruction uses `session_meta.context_window.window_id` as
    the initial-window fallback and derives `window_number = 0`,
    `first_window_id = window_id`, and `previous_window_id = None`
    internally.
    - Fork reconstruction intentionally uses the same rollout reconstruction
    path; consumers that need to distinguish copied initial-window metadata
    can use the rollout `thread_id`.
    - Legacy compactions without `window_number` still use compaction-count
    fallback accounting instead of being reset to window 0 by the
    initial-window fallback.
    - Compacted rollout metadata still takes precedence once compaction
    records exist, preserving the richer chain fields there.
    
    ## JSONL Shape
    
    Real rollout JSONL is one object per line. This example is expanded for
    readability, but shows the new initial `session_meta.context_window`
    record followed by the existing compacted rollout item shape that also
    carries window IDs:
    
    ```jsonl
    {
      "timestamp": "2026-06-22T12:00:00.000Z",
      "type": "session_meta",
      "payload": {
        "session_id": "<THREAD_ID>",
        "id": "<THREAD_ID>",
        "timestamp": "2026-06-22T12:00:00.000Z",
        "cwd": "/repo",
        "originator": "codex",
        "cli_version": "0.0.0",
        "source": "cli",
        "model_provider": "<MODEL_PROVIDER>",
        "context_window": {
          "window_id": "<INITIAL_WINDOW_ID>"
        }
      }
    }
    ...
    {
      "timestamp": "2026-06-22T12:34:56.000Z",
      "type": "compacted",
      "payload": {
        "message": "<COMPACTION_SUMMARY>",
        "replacement_history": [
          "..."
        ],
        "window_number": 1,
        "first_window_id": "<INITIAL_WINDOW_ID>",
        "previous_window_id": "<INITIAL_WINDOW_ID>",
        "window_id": "<NEXT_WINDOW_ID>"
      }
    }
    ```
    
    The nested `context_window` object is intentional: it gives rollout
    consumers a stable namespace for context-window metadata while only
    writing the non-derivable initial `window_id`. For the initial window,
    `window_number`, `first_window_id`, and `previous_window_id` are derived
    internally instead of being written to the rollout.
    
    ## Verification
    
    - `just test -p codex-protocol`
    - `just test -p codex-rollout
    recorder_materializes_on_flush_with_pending_items`
    - `just test -p codex-core reconstruct_history`
    - `just test -p codex-core
    record_initial_history_reconstructs_forked_transcript`
    - `just test -p codex-thread-store`
    - `just test -p codex-state`
    - `just test -p codex-app-server
    thread_read_returns_summary_without_turns`
    - `just test -p codex-rollout persistence_metrics`
  • core tests: rename automatic environment builder (#29728)
    ## Why
    
    Use a clearer name for what happens when this helper sets up a test
    environment.
    
    ## What
    
    - Rename the builder and its harness wrapper to use `auto_env` instead
    of `remote_env` because the helper will set up a local environment if
    configured by the build system.
  • test: branch on target OS instead of runner flavor (#29712)
    ## Why
    
    Core tests should branch on the executor's operating system, not on
    runner details such as Docker or Wine. This keeps platform behavior
    stable as new test backends are added and reserves Wine-specific skips
    for actual runner debt.
    
    ## What
    
    - Add `TestTargetOs` and target/host-aware skip helpers while keeping
    `TestEnvironment` internal.
    - Replace topology enum access with remote predicates and a narrow
    Docker accessor.
    - Migrate OS-semantic Wine skips, preserve runner-specific gaps, and
    document the skip taxonomy.
    
    ## Validation
    
    - `just test -p core_test_support`
    - `just test -p codex-core
    remote_test_env_can_connect_and_use_filesystem`
    - `bazel test //codex-rs/core:core-all-wine-exec-test
    --test_output=errors` reached test execution; unrelated existing
    view-image, path, and timing failures remain.
    - `just test -p codex-core` and `just test` reached broad test
    execution; this checkout has unrelated helper, sandbox, and timing
    failures.
  • code-mode: Rename codex_code_mode::CodeModeService (#29716)
    Mechanical rename of CodeModeService => InProcessCodeModeSession
    
    This already implements a CodeModeSession as its prime interface to
    Core. The name was vestigial _and_ confusing af when embedded inside
    core::tools::code_mode::CodeModeService
  • feat(guardian): include connected account email in app reviews (#27045)
    ## Why
    
    auto review reviews Codex App tool calls using connector metadata such
    as the app ID, name, and description. That metadata does not identify
    the account behind the OAuth connection.
    
    For Google Drive, this means auto review cannot distinguish a Drive
    connection authenticated as `user@email.com` from a personal Drive
    account. Uploading work data can therefore look like a transfer to a
    personal destination even though the connector service already knows the
    authenticated account email.
    
    ## What changed
    
    - Read `_meta._codex_apps.connected_account_email` while resolving
    approval metadata for built-in Codex App tools.
    - Include the connected account email in the structured MCP tool action
    sent to auto review.
    - Trim empty values and omit the field when the connector link has no
    account email.
    - Update existing auto review request constructors and add coverage for
    request construction and JSON serialization.
    
    ## Security
    
    Only metadata from the trusted built-in `codex_apps` MCP server is
    accepted. Custom MCP servers cannot inject a connected account email
    into auto review reviews; the new regression test verifies that spoofed
    metadata is ignored.
    
    The email is used only in auto review's private review request. This
    change does not add it to model-visible tool descriptions, app-server
    approval events, or auto review assessment/review analytics.
  • Add MCP tool call error metrics (#28976)
    [Codex Thread
    019edc37-5345-7272-92c9-bf5494cf3819](https://codex-thread-link.openai.chatgpt-team.site/thread/019edc37-5345-7272-92c9-bf5494cf3819)
    
    ## Summary
    
    - count MCP `CallToolResult.isError` responses as failed calls instead
    of successful transport-level calls
    - add `codex.mcp.call.error` with bounded `error_type` and trusted
    plugin-service `error_code` dimensions
    - record the same error classification on MCP tool-call spans while
    keeping untrusted server error text out of metric labels
    
    ## Scope
    
    - no changes to MCP routing, retries, tool behavior, configuration, or
    public APIs
    - request failures remain grouped as `mcp_request`; separating
    connection, timeout, protocol, and JSON-RPC failures requires preserving
    typed errors through the existing flattened error boundary
    
    ## Testing
    
    - `just test -p codex-core 'mcp_tool_call::tests::'` (75 passed)
    - `just fix -p codex-core`
    - `just fmt`
    - `just test -p codex-core` (2,676 passed; 80 unrelated environment
    failures from missing test binaries, sandbox signals, and read-only
    paths)
  • core: use current step environments for tools (#29547)
    ## Why
    
    With deferred executors, an environment can become ready between two
    sampling requests in the same turn. The model-visible environment
    update, advertised tools, and eventual tool execution must all describe
    the same request-time view.
    
    Otherwise, a request built while only environment B is ready can
    advertise a tool without an `environment_id`; if higher-priority
    environment A becomes ready before execution, that call could silently
    run in A instead.
    
    This PR is stacked on #29527.
    
    ## Design
    
    `run_turn` captures one `Arc<StepContext>` at each sampling-request
    boundary. That step owns the request's `TurnContext` and environment
    snapshot.
    
    - World-state environment updates and tool planning borrow that same
    step.
    - `ToolCallRuntime` retains the `Arc` while asynchronous tool calls
    execute.
    - `ToolInvocation` carries the step to handlers; its temporary `turn`
    compatibility field is derived from the same object.
    - `ToolRouter` does not retain `StepContext`; it only uses it while
    constructing the request's tool set.
    - With `DeferredExecutor` disabled, step capture keeps using the
    environments frozen at turn start.
    
    Simply: every sampling request gets one consistent picture of its
    environments, from what the model sees through where its tool calls run.
    
    ## What changed
    
    - Build environment-dependent tool specs from the current request's
    `StepContext`.
    - Use that same step for unified exec, legacy shell, `apply_patch`,
    `view_image`, and `request_permissions` execution.
    - Hide environment-backed tools, including `request_permissions`, while
    no environment is attached.
    - Resolve legacy shell paths and metadata from the selected step
    environment instead of the stale turn-start environment.
    - Capture explicit steps at non-turn-loop boundaries such as compaction,
    prompt debug, and startup prewarm.
    - Reconcile prompt-debug history from the same step used to build its
    tools.
    
    ## Follow-up
    
    - Bind yielded code-mode cells to the tool runtime that created them, so
    nested calls made after yielding continue to use the originating
    request's `StepContext`.
    
    ## Test plan
    
    - `just test -p codex-core
    deferred_executor_updates_context_and_tools_after_startup`
    - `just test -p codex-core
    environment_count_controls_environment_backed_tools`
    - `just test -p codex-core
    build_prompt_input_includes_context_and_user_message`
  • core: resolve view_image paths in selected environment (#29526)
    ## Why
    
    view_image needs to support foreign OS remote executors.
    
    ## What
    
    - resolve image paths against the selected environment as `PathUri` and
    read them through that environment's filesystem
    - keep app-server's public path field wire-compatible as
    `LegacyAppPathString`, with purpose-specific UI rendering
    - cover relative and absolute target-native paths in the core
    integration test and run the full `view_image` suite under wine-exec
    without skips
  • [codex] allow image generation with provider auth (#29513)
    ## Summary
    
    - allow the native Responses API `image_generation` tool when the active
    provider carries CCA's non-empty `x-openai-actor-authorization` header
    - preserve the Codex-managed ChatGPT auth path, scoped to providers that
    actually require OpenAI auth
    - keep generic custom providers excluded, including when unrelated
    ChatGPT credentials are cached
    - retain the existing feature, provider-capability, and
    image-input-modality gates
    
    ## Why
    
    CCA authenticates its inference requests through the active provider's
    `x-openai-actor-authorization` and `ChatGPT-Account-ID` headers, so it
    does not have a Codex-managed login session. The previous gate therefore
    hid the native hosted image-generation tool despite an authenticated
    codex-backend path.
    
    This change is intentionally limited to the native hosted tool. It adds
    no extension, MCP, plugin-service, session-source, token plumbing, or
    new provider configuration surface.
    
    ## Tests
    
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates`
    - `cargo fmt --all -- --check`
    - `git diff --check origin/main`
  • chore(core) rm AskForApproval::OnFailure (#28418)
    ## Summary
    Deletes the OnFailure variant of the `AskForApproval` enum. This option
    has been deprecated since #11631.
    
    ## Testing
    - [x] Tests pass
  • Prepare managed network sandbox context (#29456)
    ## Why
    
    Managed network configures commands to use local HTTP and SOCKS proxies.
    For commands delegated to the exec server, the proxy environment and the
    sandbox policy were prepared separately. On macOS, that meant a command
    could receive `HTTPS_PROXY=http://127.0.0.1:43123` while Seatbelt still
    denied access to port `43123`.
    
    ## What changed
    
    `NetworkProxy` now prepares the command environment and sandbox context
    together from the same runtime snapshot:
    
    ```text
    Prepared managed network
    ├── command environment: HTTPS_PROXY=http://127.0.0.1:43123
    └── sandbox context: allow outbound to 127.0.0.1:43123
    ```
    
    That context travels with remote exec requests. The exec server
    preserves the managed proxy and CA environment, and macOS Seatbelt
    allows only the prepared loopback proxy ports without enabling broad
    network access or local binding.
    
    The protocol field is optional and the existing enforcement flag remains
    in place, preserving compatibility with callers that do not send the new
    context.
  • app-server: document thread and turn IDs are UUID7 (#27714)
    It's actually a very nice property that these are UUID7s, so documenting
    them so we think twice before changing it away from UUID7s in the
    future.
  • core: use turn-owned world state for inline compaction (#29527)
    ## Why
    
    Follow-up to #29249 and its [compaction review
    thread](https://github.com/openai/codex/pull/29249#discussion_r3455055101).
    
    During a turn, environment readiness can change between sampling
    requests. Inline compaction must render the same model-visible
    `WorldState` used by the request it follows. Rebuilding that state
    during compaction can observe a newer environment, make replacement
    history disagree with what the model saw, and suppress the next
    environment update.
    
    ## What changed
    
    - Make `run_turn` own the current `Arc<WorldState>` and replace it only
    between sampling requests.
    - Build each state from an explicitly chosen environment snapshot, diff
    deferred-executor steps against the turn-owned state, and retain the
    latest state in `ContextManager` only for cross-turn and resume
    tracking.
    - Pass the exact turn-owned state into inline compaction and explicit
    new-context-window replacement.
    - Carry that state with
    `InitialContextInjection::BeforeLastUserMessage`, so replacement context
    and its stored baseline cannot come from different snapshots.
    - Remove obsolete state-recapture helpers and ambiguous TurnContext-only
    WorldState builders.
    - Add an integration test that moves an environment from starting to
    ready during a paused turn, triggers compaction, and verifies the next
    request receives the readiness update exactly once.
    
    ## Test plan
    
    - `just test -p codex-core
    deferred_executor_compaction_preserves_then_updates_environment_once`
    - `just test -p codex-core process_compacted_history`
    - `just test -p codex-core mid_turn_continuation_compaction`
    - `just test -p codex-core build_initial_context`
    - `just test -p codex-core
    ignores_session_prefix_messages_when_truncating`
  • Shut down superseded MCP managers on refresh (#29608)
    ## Summary
    
    MCP refresh replaced the published connection manager without shutting
    down the manager it superseded. If another task retained that old
    manager, its stdio MCP processes stayed alive and accumulated across
    refreshes.
    
    Atomically swap in the refreshed manager, then explicitly shut down the
    exact manager returned by the swap. Add a process-level regression test
    that retains the old manager during refresh and verifies its stdio
    process exits while the replacement remains available.
    
    ## Context
    
    Explicit cleanup was lost when manager publication moved to `ArcSwap`.
    Dropping the old manager is not a reliable shutdown boundary because
    active callers can retain its `Arc` and underlying client process
    handles.
  • [core] debounce current-time reminders by elapsed time (#29659)
    ## Summary
    - rename `reminder_interval_model_requests` to
    `reminder_interval_seconds`
    - read the configured time provider before every model request and
    inject a reminder only after the configured number of seconds has
    elapsed
    - preserve immediate first delivery and forced delivery after compaction
    changes the context window
    
    ## Tests
    - `just test -p codex-core current_time_reminder`
  • Share resumed rollout history (#28426)
    ## Summary
    
    Resuming a persisted thread currently deep-clones its complete rollout
    history several times. `InitialHistory` is retained for the app-server
    response, copied into thread persistence, and copied again by read-only
    accessors. These copies scale with the complete rollout rather than the
    bounded model context and add measurable latency for large sessions.
    
    This change stores resumed rollout history in `Arc<Vec<RolloutItem>>`.
    Rollout loading wraps the parsed vector once, while app-server response
    construction, session initialization, and thread persistence share it
    through inexpensive `Arc` clones. Read-only history access now returns a
    borrowed slice, and fork paths use `Arc::unwrap_or_clone` where they
    genuinely need mutable ownership. Rollout reconstruction also consumes
    its temporary context instead of cloning the reconstructed model
    history.
    
    The serialized representation remains unchanged. In an artificial 123 MB
    rollout benchmark, sharing resumed history reduced cold resume latency
    by roughly 9–10%. The affected crates compile with their test targets,
    all 80 thread-store tests pass, and the Bazel dependency lock remains
    valid.
  • Namespace multi-agent v2 tools under collaboration (#29067)
    ## Summary
    
    Multi-agent v2 tools now use the fixed `collaboration` namespace when
    namespace tools are available. This keeps the model-visible hint and the
    actual tool surface aligned around `functions.collaboration.*`, without
    exposing an unshipped namespace knob to users.
    
    The PR also removes the old `features.multi_agent_v2.tool_namespace`
    config/schema surface, updates the MAv2 test fixtures for namespaced
    calls, and fixes stale `TurnContext.features` references that were
    breaking `codex-core` builds.
    
    ## Changes
    
    - Expose MAv2 tools under `collaboration` instead of relying on a
    configurable namespace.
    - Remove `tool_namespace` from MAv2 TOML config, resolved config,
    validation, schema, and tests.
    - Update tool-planning and integration fixtures to assert or emit
    namespaced MAv2 tool calls.
    - Read feature state through `TurnContext.config.features` in the
    multi-agent mode context paths.
    
    ## Testing
    
    - `just write-config-schema`
    - `just test -p codex-features`
  • Fix Codex Apps auth elicitation hang (#29615)
    ## Summary
    - Require the reserved Codex Apps MCP server name to be present in the
    connection manager before treating it as host-owned.
    - Update auth elicitation tests to model an installed host-owned Codex
    Apps server without sending startup events to the test session.
    
    ## Why
    PR #29518 replaced the old host-owned flag with a name-only check. That
    made non-host-owned tests with the reserved codex_apps name enter auth
    elicitation and wait forever for a response.
  • Allow codex sandbox to consume MCP sandbox state (#29358)
    ## Summary
    
    - let `codex sandbox` accept the JSON value from
    `codex/sandbox-state-meta`
    - require the payload `permissionProfile` instead of falling back to
    ambient permissions
    - reuse the existing macOS, Linux, and Windows launch paths, treating
    external sandbox state conservatively as read-only
    - let opaque forwarders add runtime read roots and disable direct
    network access without decoding the payload
    
    Builds on #29113, which is now on `main`.
    
    ## Tests
    
    - `just test -p codex-cli debug_sandbox::tests`
    - `cargo build -p codex-rmcp-client --bin test_stdio_server`
    - `just test -p codex-core
    stdio_mcp_tool_call_includes_sandbox_state_meta`
    - `just test -p codex-mcp`
    - `just fmt`
  • Centralize Codex Apps client handling (#29528)
    ## Why
    
    Codex Apps-specific behavior is currently distributed across cache
    helpers, startup, tool conversion, and model-visible annotation. Each
    layer independently checks the reserved server name, which obscures the
    boundary between trusted host-owned connector metadata and regular MCP
    server data.
    
    Classifying the server once when `AsyncManagedClient` is created gives
    the client a single source of truth and makes the two processing paths
    explicit.
    
    ## What changed
    
    - Record whether an `AsyncManagedClient` represents the Codex Apps
    server at construction time.
    - Route startup cache loading, cache persistence, and cache telemetry
    through the Codex Apps branch.
    - Split uncached tool conversion between Codex Apps normalization and
    regular MCP metadata sanitization.
    - Split model-visible schema and plugin provenance handling along the
    same boundary.
    - Remove redundant server-name guards from helpers that are now called
    only from the Codex Apps branch.
    
    ## Verification
    
    - Preserve behavioral coverage that verifies Codex Apps connector
    metadata and the complete converted `ToolInfo` shape.
    
    ## Stack
    
    Depends on #29518.
  • [codex] Use input items for Responses Lite tools (#27946)
    When using Responses Lite, we should all use `additional_tools` and a
    developer item instead of the top level tools array & instructions
    field. This keeps things 1-to-1.
    
    Forced namespacing for _all_ tools will land in a following PR after
    some coordination & fixes in Responses API (around collisions & return
    items).
    
    The goal is to eventually expand the scope of this to _all_ requests
    from codex, but that will require larger coordination across providers &
    slower rollout.
  • Remove redundant Codex Apps manager flag (#29518)
    ## Why
    
    Codex Apps server admission is already decided before
    `McpConnectionManager` is constructed. `effective_mcp_servers` and
    `effective_mcp_servers_from_configured` remove the server when the apps
    feature or required authentication is unavailable, so storing the same
    decision on the manager duplicates state that can drift from the
    effective server map.
    
    ## What changed
    
    - Remove `host_owned_codex_apps_enabled` from `McpConnectionManager` and
    its constructor.
    - Identify the host-owned Codex Apps server by its reserved server name
    once it is present in the effective server map.
    - Remove the now-unused flag calculations and constructor arguments from
    production and test callsites.
  • [codex] stylistic changes (#29068)
    ## Summary
    
    - express remote compaction result handling as an exhaustive match
    - preserve the special `TurnAborted` path without emitting a generic
    compaction error
    - rely on the standard `test_codex` provider setup in the compaction
    budget test
    
    Follow-up to review feedback on #28707.
    
    ## Testing
    
    - `just test -p codex-core
    compaction_budget_exhaustion_aborts_without_error_or_retry`
    - `just fmt`
  • [codex] Expose service tier and reasoning effort in OTEL (#29155)
    ## Summary
    
    NVIDIA asked to measure Fast mode usage and reasoning effort from Codex
    CLI OTEL logs. Add the finalized `service_tier` and
    `model_reasoning_effort` to the existing `codex.sse_event`
    `response.completed` record.
    
    This intentionally reuses the existing completion event and leaves
    transport APIs and shared telemetry plumbing unchanged.
    
    ## Testing
    
    - `cargo build -p codex-cli --bin codex`
    - `just test -p codex-core responses_api_emits_api_request_event`
    - End-to-end with the built CLI and a local OTLP/HTTP collector:
    - Fast/high emitted `service_tier=priority` and
    `model_reasoning_effort=high` with token usage.
    - Standard/low omitted `service_tier` and emitted
    `model_reasoning_effort=low` with token usage.
  • Propagate safety buffering treatment metadata (#29473)
    ## Summary
    
    - read the request-scoped safety-buffering treatment from HTTP response
    headers and per-turn WebSocket metadata through one shared header parser
    - combine that treatment with Responses API safety-buffering signals
    - propagate `showBufferingUi` and nullable `fasterModel` through the
    existing `model/safetyBuffering/updated` app-server notification
    - update the app-server documentation and generated JSON and TypeScript
    schemas
    
    The public implementation contains no model mapping or real model
    identifier. Tests and protocol examples use generic `current-model` and
    `faster-model` placeholders only.
    
    ## Dependencies
    
    - server-side treatment evaluation:
    https://github.com/openai/openai/pull/1060247
    - initial Responses API safety-buffering propagation:
    https://github.com/openai/codex/pull/29371
    - Codex App UI: https://github.com/openai/openai/pull/1057789
    
    ## Validation
    
    - Codex API tests: 129 passed
    - focused Codex core safety-buffering integration test passed
    - app-server protocol tests passed after regenerating schema fixtures
    - Clippy fix and repository formatting completed successfully
    
    The broader app-server run compiled all changed crates and completed
    with 1,269 passing tests. Its remaining failures were unrelated
    environment limitations: macOS sandbox application was denied, one
    expected test binary was unavailable, and several existing subprocess
    tests timed out as a result.
  • mcp: accept foreign absolute cwd for remote stdio (#29493)
    ## Why
    
    Remote stdio MCP servers can run in an environment whose path convention
    differs from the Codex host. A Windows cwd such as
    `C:\Users\openai\share` is absolute for the executor but was rejected by
    a POSIX orchestrator.
    
    Built on #29501, now merged, which only clarifies the host-native
    `PathUri` constructor name.
    
    ## What changed
    
    - Deserialize MCP cwd values as `LegacyAppPathString` so config does not
    apply host path rules.
    - Interpret that spelling as host-native for local launches and convert
    it to `PathUri` at executor launch.
    - Skip host filesystem and command resolution checks for remote stdio in
    `codex doctor`.
    - Add host-independent config and executor-boundary coverage using the
    foreign path convention for each test platform.
    
    ## Validation
    
    - `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
    codex-rmcp-client` (408 passed)
    - `just test -p codex-cli -p codex-rmcp-client` (372 passed)
    - `cargo check --workspace --tests`
    - `just test` (11,311 passed; 43 unrelated environment/timing failures)
    - `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
    codex-mcp-extension -p codex-rmcp-client -p codex-tui`
  • chore: improve expired Bedrock credential errors (#28992)
    ## Why
    
    Amazon Bedrock returns a `401 Unauthorized` response containing
    `Signature expired:` when an AWS credential, including a short-lived
    `AWS_BEARER_TOKEN_BEDROCK`, has expired. Codex currently surfaces that
    response as a generic `unexpected status` error, which does not explain
    how to recover.
    
    Environment-provided bearer tokens cannot be refreshed automatically, so
    the error should direct users to refresh their AWS credentials or
    replace or remove the environment token and restart Codex. This
    classification belongs to the Amazon Bedrock provider so similar
    responses from other providers retain their existing behavior.
    
    ## What changed
    
    - Add a synchronous `ModelProvider::map_api_error` hook that defaults to
    the existing provider-neutral API error mapping, and route model
    request, stream, WebSocket, and terminal unauthorized errors through the
    active provider.
    - Override the hook for Amazon Bedrock. After preserving the structured
    status, body, URL, and request metadata, recognize `401` responses
    containing `Signature expired:` and attach actionable credential
    guidance.
    - Keep `codex-protocol` provider-neutral by representing the guidance as
    an optional `user_message`. Error rendering prefers this message while
    continuing to append the URL, request ID, Cloudflare ray, and
    authorization diagnostics.
    - Add model-provider coverage for expired signatures and negative cases,
    core coverage for provider dispatch after unauthorized recovery, and a
    TUI snapshot for the rendered error.
    
    ## Testing
    Tested with a real request with expired bedrock key:
    <img width="962" height="126" alt="Screenshot 2026-06-22 at 3 56 51 PM"
    src="https://github.com/user-attachments/assets/7e21cc7c-798e-4662-8467-7f304a2f2b59"
    />
  • fix: world state response item test (#29504)
    seems to be a merge conflict on main:
    
    > pakrym-oai introduced the stale initializer in commit 3b32d861c5, PR
    #29249.
    > Context: Owen Lin renamed metadata to
    internal_chat_message_metadata_passthrough in PR #28968. PR #29249 then
    landed afterward with the old field name, causing the compile/Clippy
    failure.
  • path-uri: clarify host-native path conversion (#29501)
    ## Why
    
    Downstream refactors are producing confusing code with this
    functionality having a very generic name. Encoding the specific
    conversion approach in the method name makes it clearer.
    
    ## What
    
    Rename `PathUri::from_path` to `PathUri::from_host_native_path` and
    update its Rust call sites.
  • [codex] Use tool search for MCP tools by default (#29486)
    ## Why
    
    MCP tools were only placed behind `tool_search` when a feature flag was
    enabled or when there were at least 100 tools. That made the model's
    tool flow depend on both rollout configuration and the number of
    installed tools.
    
    The searched-tool flow is now the intended behavior. Making it
    unconditional when the model and provider support it gives every
    supported setup the same behavior and lets us retire the feature flag
    safely.
    
    ## What changed
    
    - Defer all effective MCP tools when `tool_search` and namespaced tools
    are supported.
    - Keep exposing MCP tools directly when search cannot be used, so older
    or unsupported model/provider combinations still work.
    - Mark `tool_search_always_defer_mcp_tools` as removed and ignore old
    configured values.
    - Keep plugin filtering, app-only filtering, file handling, and MCP
    calls working through the searched-tool flow.
    
    ## Why many tests changed
    
    Many tests used to act as if the model could see MCP tools in its first
    request and call them immediately. That is no longer the real flow: the
    model first receives `tool_search`, searches for a tool, receives the
    matching MCP tool, and then calls it in the next request.
    
    The tests therefore needed an extra search step, and checks for tool
    names, descriptions, and input fields had to move from the first request
    to the search result. These are not separate product changes; they make
    the tests follow what the model will actually see after this change. The
    plugin tests still check which tools are allowed and where they came
    from, the file tests still check upload fields and behavior, and the MCP
    round-trip test still checks a successful call from start to finish.
    
    ## Tests
    
    - `just test -p codex-features`
    - Focused `codex-core` tests for MCP exposure and tool planning
    - `just test -p codex-core explicit_plugin_mentions`
    - `just test -p codex-core stdio_server_round_trip`
    - Focused `codex-core` tests for tool search, app-only tools, and MCP
    file uploads
  • feat(core): store turn_id on ResponseItem metadata (#28360)
    ## Description
    
    This PR is a followup to https://github.com/openai/codex/pull/28355 and
    starts assigning `internal_chat_message_metadata_passthrough.turn_id` to
    durable Responses API items created during a turn.
    
    The goal is that those items keep the `turn_id` that introduced them
    when Codex resends stateless HTTP context, reconstructs history for
    resume/fork paths, or reuses websocket response state.
    
    ## What changed
    
    - Set `internal_chat_message_metadata_passthrough.turn_id` when missing
    as response items enter durable history, initial/replacement history,
    inter-agent communication history, and local compaction summaries.
    - Preserve existing item turn IDs instead of overwriting them during
    persistence, resume reconstruction, compaction, forked history, and
    websocket incremental reuse.
    - Keep `compaction_trigger` fieldless because it is a request control,
    not a durable response item.
    - Update focused history/request assertions and fixtures for stateless
    requests, websocket incrementals, compaction, thread injection, prompt
    debug, and related CI coverage.
  • [codex] replace remote images with model-visible error text (#29417)
    ## What
    
    This PR will extend the existing centralized image-preparation path to
    replace HTTP(S) image inputs with a model visible error message. It
    won't "ruin" and break existing rollouts, but it will deprecate support
    for the pathway. App server clients should no longer use HTTP image urls
    if they'd like to upgrade.
    
    The HTTP image url pathway is currently resolved in the responsesapi. It
    is slow and not reccomended.
    
    ## Behavior
    
    - HTTP(S) image URL: replace with `input_text`
    - data URL: use the existing decode and resize path
    - other image URL schemes: leave unchanged
    
    This intentionally does not change app-server ingress. That validation
    remains a follow-up.
    
    ## Test plan
    
    - `just test -p codex-core -E
    'test(/image_preparation|prepares_image_failures_before_history_insertion|prepares_resumed_history_before_installing_it|responses_lite_prepares_images/)'`
    — 7 passed
    - `just fix -p codex-core`
    - `just fmt`
  • core: wrap token budget window context (#29494)
    Token-budget initial context carries thread and context-window lineage
    that the model should treat as one structured context-window block.
    Wrapping it in `<context_window>` makes that boundary explicit while
    preserving the existing window id content.
    
    Before this change, the window identifiers were injected as an untagged
    developer text fragment:
    
    ```text
    Thread id <THREAD_ID>.
    First context window id: <FIRST_WINDOW_ID>
    Current context window id: <WINDOW_ID>
    Previous context window id: <PREVIOUS_WINDOW_ID>
    ```
    
    After this change, the same payload is wrapped as a context-window
    block:
    
    ```text
    <context_window>
    Thread id: <THREAD_ID>
    First context window id: <FIRST_WINDOW_ID>
    Current context window id: <WINDOW_ID>
    Previous context window id: <PREVIOUS_WINDOW_ID>
    </context_window>
    ```
    
    This adds shared `CONTEXT_WINDOW_*_TAG` protocol constants, updates
    `TokenBudgetContext` to render with those markers, treats the new
    wrapper as contextual developer content when mapping history, and
    refreshes the token-budget request-shape assertions and snapshot.
    
    Verification:
    - `just test -p codex-core token_budget`
    - `just test -p codex-core
    recognizes_context_window_as_contextual_developer_content`
  • [codex] migrate environment context to model world state (#29249)
    ## Why
    
    Environment context is model-visible state, but it is currently
    assembled from transient turn values and diffed through
    environment-specific paths. That makes initial injection, turn-to-turn
    updates, and changes that happen within a turn use different baselines.
    
    This PR introduces the smallest useful model world-state slice:
    environments only, with one in-memory baseline and one renderer for full
    state and diffs.
    
    ## What changed
    
    - Add a typed `WorldState` container whose sections render fragments
    relative to an optional previous value. Full rendering uses the same
    diff path with no previous state.
    - Replace the parallel `EnvironmentContext` representation with an
    `EnvironmentsState` section keyed by environment ID and rendered in
    deterministic order.
    - Preserve the legacy single-environment output while supporting
    multiple environments, starting environments, unavailable tombstones,
    and changes to persisted turn-context values.
    - Store the latest complete `WorldState` on `ContextManager` and use it
    for both turn-boundary and mid-turn environment diffs.
    - Build initial and post-compaction context from the same world-state
    builder, then retain the rendered state as the next baseline.
    - Seed the in-memory baseline from the latest `TurnContextItem` when
    resuming an existing rollout; the world state itself is not serialized.
    - Keep non-world settings updates on their existing path and merge
    rendered world-state fragments at the session consumer.
    
    ## Known limitation
    
    A legacy `TurnContextItem` only reconstructs the primary environment as
    `local`; it cannot faithfully recover a remote-primary environment ID
    after resume. Live state uses the exact environment IDs once a complete
    baseline is established.
    
    ## Test plan
    
    - `just test -p codex-core world_state`
    - `just test -p codex-core record_context_updates`
    - `just test -p codex-core deferred_executor_`
    - `just test -p codex-core build_initial_context`
    - `just test -p codex-core rollout_reconstruction`
    - `just test -p codex-core
    process_compacted_history_reinjects_full_initial_context`
  • Register full CDP requirements feature (#28769)
    register cdp requirements feature flag
  • fix(config): address permission profile review follow-ups (#29479)
    ## Summary
    
    - rename `Config::permission_profile_allowed` to
    `is_permission_profile_allowed`
    - use `BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS` in the TUI and
    its assertion
    - follow up on the late review comments from #26678
    
    The previous `:danger-no-sandbox` value was an invalid built-in profile
    ID. #26678 corrected it to `:danger-full-access`; this PR centralizes
    the value to prevent future drift.
    
    ## Testing
    
    - Not run per request; `cargo fmt` only
    
    Co-authored-by: Codex <noreply@openai.com>
  • permission profiles: expose availability to clients (#26678)
    ## Why
    
    `permissionProfile/list` currently advertises every built-in and
    configured profile even when effective enterprise requirements prevent
    selecting it. That forces each client to reconstruct policy from
    lower-level requirement fields, which is easy to miss and difficult to
    keep consistent.
    
    The catalog should remain complete so clients can explain that an option
    was disabled by an administrator, while also reporting whether each
    profile is selectable.
    
    ## What
    
    - Add an `allowed` field to each permission profile summary.
    - Build a shared catalog from the effective config and current
    requirements, including `allowed_sandbox_modes`, `allowed_permissions`,
    and filesystem restrictions.
    - Use the shared catalog in app-server and the TUI so disallowed
    profiles remain visible but cannot be selected.
    - Use the canonical `:danger-full-access` profile ID in the TUI.
    - Update the app-server schemas, API documentation, behavioral tests,
    and TUI snapshots.
    
    ## Scope
    
    This PR targets `main` directly and is independent of #24852. It
    preserves the current behavior where built-in profiles are constrained
    by sandbox-mode requirements and `allowed_permissions` applies to
    configured profiles.
    
    ## Testing
    
    - `just test -p codex-core
    permission_profile_catalog_marks_profiles_disallowed_by_requirements`
    - `just test -p codex-app-server permission_profile_list`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-tui profile_permissions`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
    - `just fmt`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
    Co-authored-by: Joey Trasatti <joey.trasatti@openai.com>
  • [codex] configure rollout budget reminder thresholds (#29423)
    ## Summary
    
    Instead of:
    
        reminder_interval_tokens = 65_536
    
    allow users to configure explicit remaining-token reminder thresholds:
    
    reminder_at_remaining_tokens = [65_536, 32_768, 16_384, 8_192, 4_096,
    2_048, 1_024, 512]
    
    ## Validation
    
    - CARGO_INCREMENTAL=0 just test -p codex-core rollout_budget: 9 passed
    - just fix -p codex-core
    - just fmt
  • PAC 2 - Add shared auth system proxy contract (#26707)
    ## Summary
    
    Stacked on #26706.
    
    Adds the shared auth/system-proxy contract that later platform resolver
    PRs plug into. This PR moves Codex-owned auth and startup HTTP clients
    through a common route-aware boundary, but does not yet add Windows or
    macOS system proxy resolution.
    
    The default path remains unchanged when `respect_system_proxy` is absent
    or disabled.
    
    ## Implementation
    
    - Adds `codex-client/src/outbound_proxy.rs` with the shared
    route-selection model:
      - `OutboundProxyConfig`;
      - `ClientRouteClass`;
      - `RouteFailureClass`;
      - `build_reqwest_client_for_route`.
    - Preserves the existing reqwest/default-client behavior when no route
    config is supplied.
    - Uses the fixed MVP routing policy when route config is supplied:
    platform system/PAC/WPAD discovery, then explicit env proxy variables,
    then direct connection.
    - Keeps platform-specific system discovery behind the shared client
    boundary. This PR provides the contract and fallback behavior; later
    resolver PRs plug in Windows and macOS discovery.
    - Adds `login::AuthRouteConfig` so auth call sites depend on a small
    policy type instead of platform resolver details.
    - Maps the resolved `Config.respect_system_proxy` boolean into
    `AuthRouteConfig` for auth-owned clients.
    - Wires the route config through browser login, device-code login,
    access-token login, login status, logout/revoke, token refresh, API-key
    exchange, app-server account login, TUI/app startup, cloud-config
    bootstrap, cloud tasks, plugin auth, and exec startup config loading.
    
    ## End-user behavior
    
    - No behavior changes by default.
    - When `respect_system_proxy = true`, auth-owned clients opt into the
    shared route-aware client path.
    - On platforms without a resolver implementation in this PR, system
    discovery is unavailable and the route-aware path falls back to explicit
    env proxy handling, then direct connection.
    - Custom CA handling remains separate from proxy route selection and
    still runs through the shared client builder.
    - No proxy URLs, PAC contents, or resolved platform details are exposed
    through the public config surface introduced here.
    
    ## Tests
    
    Adds or updates coverage for:
    
    - preserving default auth-client fallback behavior when no route config
    is provided;
    - injected environment-proxy fallback without mutating process
    environment;
    - existing login-server E2E flows using explicit `auth_route_config:
    None` to guard unchanged default behavior;
    - updated auth manager, login, logout, cloud-config, startup, and
    plugin-auth call sites passing route config explicitly.
  • core: remove unused permissions cwd plumbing (#29468)
    ## Why
    
    `compile_scoped_filesystem_pattern()` accepted a `_policy_cwd` parameter
    even though scoped glob compilation no longer uses the policy working
    directory. Keeping that unused argument forced the surrounding
    permissions compilation path to keep forwarding `policy_cwd` through
    call sites that did not need it, making the API look more dependent on
    cwd resolution than it is.
    
    ## What changed
    
    Removed the unused cwd parameter from
    `compile_scoped_filesystem_pattern()` and the callers that only
    forwarded it: `compile_filesystem_permission()`,
    `compile_permission_profile()`, and
    `compile_permission_profile_selection()`. Workspace root resolution
    still keeps `policy_cwd`, because that path still resolves relative
    roots against the active policy cwd.
    
    Relevant code:
    [`codex-rs/core/src/config/permissions.rs`](https://github.com/openai/codex/blob/b8b9816102e064dae4488ec130cf560f63c1ab78/codex-rs/core/src/config/permissions.rs#L346).
    
    ## Verification
    
    - `just test -p codex-core config::permissions`
    - `just test -p codex-core` was also run after building
    `test_stdio_server`; it passed the touched permissions coverage but
    still reported unrelated existing failures in `cli_stream` and shell
    snapshot tests.
  • [codex] Start the guardian child session when parent session is started (#27982)
    ## Why
    
    The first auto-review currently creates its Guardian child session on
    demand, adding avoidable latency before the review can begin. Creating
    the ordinary Guardian child during parent-session initialization lets
    that child use the existing session startup WebSocket prewarm before the
    first escalation. This does not introduce a Guardian-specific prewarm
    mechanism.
    
    ## What changed
    
    - initialize the existing Guardian review-session manager owned by
    `Session` when a thread starts with auto-review enabled and an approval
    policy that routes to Guardian
    - use the standard Guardian child-session construction and the existing
    session startup WebSocket prewarm
    - preserve the existing reuse-key invalidation and lazy creation
    fallback when startup initialization fails or the effective review
    configuration changes
    - add an integration test that verifies normal root-session startup
    emits a Guardian `generate=false` prewarm request
    
    ## Benchmark
    
    I compared release builds against main. Each prompt first ran a
    non-escalated `sleep 3`, then requested an escalated marker command.
    
    | binary | count | avg Guardian duration | median Guardian duration |
    avg Guardian TTFT |
    |---|---:|---:|---:|---:|
    | origin-main | 10 | 4008.7 ms | 3949.5 ms | 3746.5 ms |
    | session-fix | 10 | 2865.0 ms | 2594.0 ms | 2492.7 ms |
    
    Guardian duration fell by 28.5% and Guardian TTFT fell by 33.5%. These
    measurements cover Guardian review latency; they do not measure parent
    thread-start latency.
  • core: rename metadata -> internal_chat_message_metadata_passthrough (#28968)
    ## Description
    This PR cuts Codex over from generic `ResponseItem.metadata` (introduced
    here: https://github.com/openai/codex/pull/28355) to
    `ResponseItem.internal_chat_message_metadata_passthrough`, which is the
    blessed path and has strongly-typed keys.
    
    For now we have to drop this MAv2 usage of `metadata`:
    https://github.com/openai/codex/pull/28561 until we figure out where
    that should live.
  • Report remote sandbox denials semantically (#29424)
    ## Why
    
    #29113 moved remote sandbox setup and enforcement to the exec server.
    That gives the executor ownership of the platform-specific work: a Linux
    executor chooses and runs a Linux sandbox even when the Codex
    orchestrator is running on macOS or Windows.
    
    It also means the orchestrator no longer knows which concrete sandbox
    the executor selected. When that sandbox blocks a remote command, the
    orchestrator currently sees only a failed process and can treat the
    denial as an ordinary command failure. The existing sandbox approval and
    retry path is then skipped.
    
    This PR lets the executor report one portable fact:
    
    > This command probably failed because the executor sandbox blocked it.
    
    The executor keeps its concrete sandbox type private. The protocol sends
    only the semantic result.
    
    ## Example
    
    Suppose a local macOS Codex session asks a Linux devbox to write outside
    the allowed workspace.
    
    Before this PR:
    
    ```text
    Linux sandbox blocks the write
        -> remote process exits with "Permission denied"
        -> local orchestrator sees an ordinary command failure
        -> the normal sandbox approval and retry path can be skipped
    ```
    
    With this PR:
    
    ```text
    Linux sandbox blocks the write
        -> executor reports sandboxDenied: true
        -> unified exec returns UnifiedExecError::SandboxDenied
        -> the existing approval prompt is shown
        -> an approved retry runs through the existing unsandboxed retry path
    ```
    
    ## What changes
    
    ### The executor remembers its selected sandbox
    
    The prepared remote process now retains the executor-selected
    `SandboxType`. This value never crosses the executor boundary.
    
    Commands started without a sandbox retain `SandboxType::None` and are
    never reported as sandbox denials.
    
    ### The executor uses the existing denial heuristic
    
    The existing local denial heuristic moves from `codex-core` into the
    shared `codex-sandboxing` crate.
    
    When a sandboxed remote process exits, the executor:
    
    1. waits the same short output grace period used by local unified exec;
    2. reads the output currently available in the existing retained output
    buffer;
    3. runs the existing heuristic using the exit code and common denial
    messages;
    4. stores the yes/no result before publishing the process exit.
    
    This deliberately matches the old local unified-exec behavior. It does
    not add a new streaming classifier, another output buffer, or stronger
    output-retention guarantees.
    
    ### The protocol reports a portable boolean
    
    `process/read` gains `sandboxDenied`:
    
    ```json
    {
      "exited": true,
      "exitCode": 1,
      "closed": false,
      "sandboxDenied": true
    }
    ```
    
    The field defaults to `false` when an older executor omits it. The
    response does not expose the executor sandbox implementation or
    executor-native paths.
    
    ### Unified exec uses the existing error path
    
    The exec-server client carries `sandboxDenied` into the unified process
    state. If it is true, unified exec returns the existing `SandboxDenied`
    error instead of trying to classify remote output using an
    orchestrator-side sandbox type.
    
    Remote process exit remains visible as soon as the process exits. This
    PR does not wait for stdout or stderr to close and does not change the
    existing process lifecycle.
    
    ## Scope
    
    This PR is intentionally limited to matching the existing local
    unified-exec behavior for the initial command execution path.
    
    It does not add:
    
    - incremental denial tracking across the full output stream;
    - new denial handling for commands completed later through
    `write_stdin`;
    - new guarantees for preserving the semantic flag during the narrow
    reconnect-recovery race.
    
    Those can be considered separately if the same behavior is added for
    local execution.
    
    ## Test coverage
    
    One remote end-to-end integration test covers the complete intended
    flow:
    
    ```text
    remote read-only sandbox
        -> denied write
        -> executor reports the denial
        -> Codex requests approval
        -> user approves
        -> retry succeeds on the remote executor
    ```
    
    Existing lifecycle coverage continues to verify that remote process exit
    is reported before late output streams close.
  • [codex] Centralize Plugin Analytics Metadata (#27102)
    This PR moves construction of `PluginTelemetryMetadata` from loader and
    model helpers into `PluginsManager`, which already owns installed plugin
    state and will eventually perform remote identity enrichment. The
    metadata type remains in `codex-plugin`, and serialized analytics events
    remain unchanged.
    
    ## Before
    
    ```mermaid
    flowchart LR
        subgraph Events["Analytics event paths"]
            direction TB
            Lifecycle["Local install / uninstall"]
            Config["Enable / disable"]
            Remote["Remote install"]
            Used["Plugin used"]
        end
    
        subgraph Construction["Metadata construction"]
            direction TB
            Loader["Loader telemetry helpers"]
            Summary["PluginCapabilitySummary::telemetry_metadata"]
            Override["Caller adds remote_plugin_id"]
        end
    
        Metadata["PluginTelemetryMetadata"]
    
        Lifecycle --> Loader
        Config --> Loader
        Remote --> Loader
        Loader -->|"local events"| Metadata
        Loader -->|"remote install"| Override
        Override --> Metadata
        Used --> Summary
        Summary --> Metadata
    ```
    
    Telemetry metadata was constructed through loader helpers, a
    capability-summary method, and a remote-install call-site override.
    
    ## After
    
    ```mermaid
    flowchart LR
        subgraph Events["Analytics event paths"]
            direction TB
            Lifecycle["Local install / uninstall"]
            Config["Enable / disable"]
            Remote["Remote install"]
            Used["Plugin used"]
        end
    
        Manager["PluginsManager — single construction owner"]
        Metadata["PluginTelemetryMetadata"]
    
        Lifecycle --> Manager
        Config --> Manager
        Remote -->|"authoritative remote ID"| Manager
        Used -->|"capability summary"| Manager
        Manager --> Metadata
    ```
    
    Every analytics path delegates metadata construction to
    `PluginsManager`. Remote install still supplies its authoritative
    backend ID explicitly.
    
    ## What Changes
    
    - Make loader code return a focused plugin capability summary instead of
    constructing analytics metadata.
    - Centralize immutable plugin telemetry metadata construction in
    `PluginsManager`.
    - Route local install/uninstall, remote install, enable/disable, and
    plugin-used emitters through the manager.
    - Preserve the current serialized analytics contract exactly.
    
    Normal metadata still has no remote override. Remote install continues
    to provide its authoritative backend ID explicitly, so the existing
    serializer continues reporting that ID through `plugin_id`.
    Snapshot-based enrichment is intentionally deferred to the final PR.
    
    ## Testing
    
    - `just test -p codex-core-plugins` (238 tests passed)
    - `just test -p codex-plugin` (3 tests passed)
    - Scoped Clippy/compile checks passed for `codex-plugin`,
    `codex-core-plugins`, `codex-app-server`, and `codex-core`.
    
    ## Split Overview
    
    ```text
    main
    ├── #27093  Debug analytics capture                 (merged)
    ├── #27099  Non-mutating plugin smoke               (merged)
    ├── #27100  Remote install/uninstall smoke          (merged)
    └── #27102  Plugin telemetry metadata refactor      ← you are here
        └── #27669  Persist remote plugin identity
    
    After #27102 and #27669 merge:
    └── Final PR: add explicit local and remote IDs to plugin analytics
    ```
    
    Review order and dependencies:
    
    1. [#27093 Add debug-only analytics event
    capture](https://github.com/openai/codex/pull/27093) (merged)
    2. [#27099 Add a plugin analytics smoke
    workflow](https://github.com/openai/codex/pull/27099) (merged)
    3. [#27100 Add a remote plugin analytics mutation smoke
    workflow](https://github.com/openai/codex/pull/27100) (merged)
    4. This metadata refactor, independent and based on `main`
    5. [#27669 Persist remote plugin
    identity](https://github.com/openai/codex/pull/27669), stacked on this
    PR
    6. Final remote-ID behavior PR, created after the prerequisites merge
    
    The original [#26281](https://github.com/openai/codex/pull/26281)
    remains open as the aggregate reference until the final replacement PR
    is published.
  • remove flag for image preparation (#29429)
    ## What
    
    - make Fjord's centralized response-item image preparation unconditional
    for new and resumed history
    - have local user images and `view_image` outputs always defer decoding
    and resizing to that path
    - retain `resize_all_images` as an ignored, removed compatibility key
    for released clients
    - delete the flag-off producer paths and obsolete policy-specific tests
    
    ## Why
    
    Centralized preparation is now the intended image path. Keeping the
    runtime feature checks also kept two image-processing implementations
    alive and allowed client config to select the legacy behavior.
    
    This is a clean replacement for #28975, rebuilt from the latest `main`.
    
    ## How
    
    `prepare_response_items` now runs whenever items enter history and
    whenever persisted history is reconstructed. Producers emit deferred
    image data, so malformed images become the existing model-visible
    placeholder instead of failing the session at the producer.
    
    ## Test plan
    
    - `just fmt`
    - `just fix -p codex-core -p codex-features`
    - `just test -p codex-features` — 52 passed
    - focused affected `codex-core` set — 20 passed
    - `just test -p codex-core handle_accepts_explicit_high_detail` — 1
    passed
    - full `just test -p codex-core` attempt — 2,723 passed; 88 unrelated
    environment failures from read-only `~/.codex` SQLite state and
    unavailable integration helper binaries
  • fix(core): restore thread_source in x-codex-turn-metadata (#29455)
    ## Description
    
    Restore `thread_source` in `x-codex-turn-metadata`.
    
    Inadvertently removed `thread_source` from `x-codex-turn-metadata` in
    https://github.com/openai/codex/pull/27122 - didn't realize it was a
    top-level thread app-server API field, not passed in
    `responsesapi_client_metadata`.
    
    This also reserves the key so `responsesapi_client_metadata` cannot
    override it.
  • core: refresh environment context before sampling (#29073)
    ## Why
    
    Nonblocking environment snapshots allow a turn to reach the model while
    a remote environment is still starting. The initial context can describe
    that environment as still loading, but nothing currently refreshes the
    model-visible environment context when startup finishes during the same
    turn.
    
    This adds the first request-scoped reconciliation slice on top of
    #28683. It is gated by `DeferredExecutor` and intentionally updates only
    model-visible environment context; tools and other environment-derived
    state will migrate separately.
    
    ## What
    
    - Add a minimal `StepContext` containing the environment snapshot
    captured before each sampling request.
    - Render attached environments with their resolved shell and starting
    environments with `still loading`.
    - Track the latest environment state recorded in model history and
    append a bounded update only when it changes.
    - Seed that baseline from full initial context so ready-at-start
    environments are not duplicated.
    - Clear the in-memory baseline when history is rewritten so replacement
    history can be refreshed safely.
    
    ## Testing
    
    - `just test -p codex-core deferred_executor`
    - `just test -p codex-core
    environment_context_baseline_deduplicates_until_history_is_replaced`
    
    The integration coverage verifies that a pending environment reaches the
    first request, the ready state reaches the next request, later requests
    do not duplicate it, and ready-at-start environments remain
    single-injected.
    
    <details>
    <summary>Live verification</summary>
    
    - Connected to a real remote executor with startup deliberately delayed
    and forced three sampling requests in one turn.
    - Inspected the raw model inputs: request 1 showed the remote
    environment as `still loading`, request 2 appended its ready shell and
    cwd, and request 3 contained no duplicate ready update.
    - With the feature disabled, startup waited for the delayed executor and
    the first request contained only the ready environment.
    - With a synchronously ready environment and the feature enabled, the
    first request contained one environment context with no duplicate.
    - Executed `pwd` and read a marker file through the remote process
    runner; the command exited successfully and returned the remote cwd and
    marker contents.
    
    </details>