Commit Graph

7722 Commits

  • Group Codex Apps client setup (#29583)
    ## Why
    
    `McpConnectionManager::new` classified the Codex Apps server twice: once
    to create its tools cache context and again to select its runtime
    authentication provider. Keeping those decisions separate makes it
    harder to see that they belong to the same server-specific setup path.
    
    ## What changed
    
    - Group Codex Apps cache and authentication setup under one explicit
    branch.
    - Keep regular MCP server setup in the corresponding `else` branch.
    - Limit environment bearer-token inspection to the Codex Apps path where
    it affects runtime authentication.
  • Remove redundant Codex Apps cache guard (#29575)
    ## Why
    
    Codex Apps cache writes are already restricted to Codex Apps call paths:
    startup invokes the helper only from the Codex Apps branch, and hard
    refresh operates on the reserved Codex Apps server directly. Rechecking
    the server name inside the cache helper duplicates that classification
    and leaves the helper with an argument that cannot change valid
    behavior.
    
    ## What changed
    
    - Remove the redundant server-name check and parameter from the cache
    writer.
    - Rename the helper to `write_codex_apps_tools_cache` to reflect its
    narrower contract.
    - Update production and test callsites to use the simplified API.
  • Handle additional tools in image URL validation (#29577)
    ## Why
    
    `ResponseItem::AdditionalTools` was added without updating app-server
    image URL validation. The exhaustive match therefore prevents app-server
    and downstream targets from compiling on `main`.
    
    ## What changed
    
    Treat `AdditionalTools` like the other response items that cannot
    contain input-image URLs.
  • 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.
  • [codex] Reject unlowered PowerShell AST regions (#24092)
    ## Why
    
    On Windows, Codex uses a PowerShell safe-command classifier to decide
    whether a command is read-only enough to run without additional
    approval. The classifier lowers `EndBlock.Statements` into argv-like
    command words and checks those words against a safelist.
    
    PowerShell can execute code stored elsewhere in the AST. Parameter
    defaults, named blocks, `using` preambles, and top-level `trap` handlers
    are not represented in the lowered statement list. Ignoring those
    regions can make a side-effecting script look like a read-only command.
    
    ## What
    
    Fail closed whenever a PowerShell script contains executable AST content
    that the current lowering does not represent.
    
    ## How
    
    - Return `unsupported` for parameter, dynamic-parameter, begin, process,
    and clean blocks.
    - Return `unsupported` for `using module` and `using assembly`
    preambles.
    - Return `unsupported` for non-empty `EndBlock.Traps` collections.
    - Preserve compatibility with Windows PowerShell 5.1 by looking up
    `CleanBlock` dynamically.
    - Treat `unsupported` as a failure to prove that the command is safe,
    routing it through the normal approval path.
    - Add parser-level and end-to-end regressions for parameter blocks,
    named blocks, using statements, and trap handlers.
    
    This does not make these PowerShell forms invalid or prevent them from
    running. It prevents automatic safe-command approval when the classifier
    cannot account for all executable behavior.
    
    ## Testing
    
    - `just test -p codex-shell-command`
    - Windows CI exercises the parser and end-to-end safe-command
    regressions against a real PowerShell installation.
    
    ---------
    
    Co-authored-by: viyatb-oai <viyatb@openai.com>
  • 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`
  • PAC 4 - Add macOS system proxy resolver (#26709)
    ## Summary
    
    Stacked on #26708.
    
    Adds the macOS implementation of the shared system-proxy contract. This
    allows Codex-owned auth clients to use the route macOS selects for each
    auth URL through SystemConfiguration and CFNetwork, including PAC and
    WPAD results.
    
    The `respect_system_proxy` feature is disabled by default, so existing
    client behavior remains unchanged unless explicitly enabled.
    
    ## Implementation
    
    - Adds the macOS-only `system-configuration` dependency to
    `codex-client`.
    - Dispatches system-proxy resolution to `outbound_proxy/macos.rs` on
    macOS.
    - Reads system proxy settings from `SCDynamicStore` and resolves the
    target URL with `CFNetworkCopyProxiesForURL`.
    - Executes PAC URLs and inline PAC JavaScript through a bounded run loop
    with a five-second timeout.
    - Handles `DIRECT`, HTTP proxies, and CFNetwork HTTPS entries using HTTP
    CONNECT; unsupported SOCKS entries map to `UnsupportedProxyScheme`.
    - Builds concrete proxy URLs from host and port entries, including IPv6
    host bracketing.
    - Maps results into the shared `SystemProxyDecision::{Direct, Proxy,
    Unavailable}` contract.
    - Hashes URL-specific cache keys so PAC decisions remain distinct
    without retaining raw request URLs or query strings.
    
    ## End-user behavior
    
    - Disabled/default: existing client behavior is unchanged.
    - Enabled with `[features.respect_system_proxy]`:
      - macOS auth clients honor system proxy configuration, PAC, and WPAD;
      - valid OS/PAC `DIRECT` decisions use a direct connection;
    - unavailable system resolution falls back to explicit environment proxy
    variables, then `DIRECT`, through the shared contract from #26707.
    - Unsupported proxy schemes are not silently translated into another
    route.
    - Custom CA handling remains separate from proxy selection.
    - Known limitation: only the first supported system/PAC candidate is
    used. Subsequent proxy or `DIRECT` candidates are not attempted after a
    connection failure. This matches the current Windows behavior and leaves
    room for future ordered-fallback support.
    
    ## Tests
    
    - `just test -p codex-client` — 34 tests passed.
    - `just clippy -p codex-client`
    - `just fmt`
    - `just bazel-lock-check`
  • Make formatter output quiet on success (#29467)
    ## Why
    
    `just fmt` is quite noisy even on successful runs.
    
    ## What
    
    Only print output when a formatter fails.
    
    - Buffer output from each formatter and print only a failed command and
    its diagnostics.
    - Prefix the `justfile` driver invocations with `@` so Just does not
    echo the command itself.
    - Retain rustfmt stderr on failure and cover silent-success and
    failure-reporting behavior.
    
    ## Validation
    
    - Confirmed `just fmt` and `just fmt-check` both exit successfully with
    empty stdout and stderr.
  • 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"
    />
  • [codex] reject remote images at app-server ingress (#29419)
    ## Stack
    
    Stacked on #29417. Review and land that PR first.
    
    ## Summary
    
    - reject HTTP(S) image URLs in the handlers for `turn/start` and
    `turn/steer`
    - validate `thread/inject_items` after its existing
    JSON-to-`ResponseItem` conversion, so each item is deserialized once
    - turn invalid dynamic-tool image responses into the existing
    unsuccessful text fallback; the model receives the validation message as
    the function output
    - leave `thread/resume.history` compatible with legacy history; #29417
    replaces remote images before model input
    - continue accepting inline data URLs and `localImage` inputs
    - keep this policy in app-server; this PR does not add a shared protocol
    API or change core image preparation
    
    ## Test plan
    
    - `just test -p codex-app-server -E
    'test(/request_handlers_reject_remote_image_urls|dynamic_tool_remote_image_response_becomes_model_visible_error|dynamic_tool_call_round_trip_sends_content_items_to_model|turn_start_tracks_turn_event_analytics|standalone_image_edit_uses_recent_pathless_image/)'`
    (5 passed)
    - `just fix -p codex-app-server`
    - `just fmt`
  • TUI Plugin Sharing 4 - cover remote plugin catalog flows (#26704)
    Remote plugin catalogs now span workspace, shared, and local sources, so
    their TUI behavior needs focused regression coverage across loading,
    navigation, actions, and refreshes.
    
    This PR:
    
    - Covers product labels and rendered loading/error states for Workspace,
    Shared with me, Shared with me (link), and Local tabs, including tab
    persistence across refresh and detail navigation.
    - Covers remote/local deduplication, Installed-tab remote detail
    routing, marketplace load-error handling, and disabled install/uninstall
    navigation.
    - Adds a full detail snapshot for local shared-plugin metadata plus
    focused snapshots for marketplace labels and admin-disabled status.
    - Verifies shared plugins remain eligible for mentions and successful
    uninstalls trigger a catalog refresh.
  • 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`
  • [plugins] Add dark-mode logo metadata (#29488)
    Adds additive dark-mode plugin logo metadata across manifests, remote
    catalogs, and the app-server protocol while keeping uninstalled Git
    listings free of synthetic local paths.
    
    Supersedes #28945. This replacement uses an upstream branch so trusted
    CI can use the repository-provided remote Bazel configuration.
    
    ## Current state
    
    Plugin interfaces expose only the default logo asset. Clients therefore
    cannot select a dedicated dark-mode logo even when a plugin provides
    one.
    
    ## What this PR changes
    
    - Adds nullable `logoDark` and `logoUrlDark` fields to
    `PluginInterface`.
    - Resolves local `interface.logoDark` assets and maps remote
    `logo_url_dark` values.
    - Removes path-backed interface assets, including `logoDark`, from
    uninstalled Git fallback listings until the plugin has a real local
    root.
    - Updates the bundled plugin validator and manifest reference.
    - Regenerates the app-server JSON schemas and TypeScript types.
    
    Local manifests expose `interface.logoDark` as a package-relative asset
    path. Remote catalog responses expose `logo_url_dark`. These values map
    into separate app-server fields so clients can preserve local-path and
    remote-URL handling.
    
    ## Risk
    
    The fields are additive and nullable, so existing clients retain their
    current logo behavior. The main risks are an incomplete mapping path or
    exposing a synthetic local path for an uninstalled Git plugin.
    Local-manifest, remote-catalog, fallback-listing, protocol
    serialization, and app-server integration tests cover those paths.
    
    Spiciness: 2/5
    
    ## Testing
    
    - `just write-app-server-schema`
    - `just fmt`
    - Regression test first failed with `logo_dark` resolved to
    `/assets/logo-dark.png`, then passed after the fallback-listing fix.
    - `just test -p codex-core-plugins` (267 tests passed)
    - `just test -p codex-app-server 'suite::v2::plugin'` (114 tests passed)
    - `just test -p codex-app-server-protocol -p codex-core-plugins -p
    codex-plugin -p codex-skills` (517 tests passed before the follow-up)
    - `just test -p codex-tui plugin` (47 tests passed)
    - Validated a local plugin manifest containing `interface.logoDark` with
    the bundled validator.
    
    ## Manual verification
    
    Create a local plugin with both `interface.logo` and
    `interface.logoDark`, then call `plugin/list` or `plugin/read`. Confirm
    the response contains separate `logo` and `logoDark` paths. For a remote
    catalog entry, confirm `logoUrlDark` is populated from `logo_url_dark`.
    For an uninstalled Git marketplace entry, confirm path-backed interface
    assets remain absent until installation.
    
    Issue: N/A - coordinated maintainer change.
  • [codex] Update esbuild to 0.28.1 (#29489)
    ## Why
    
    The TypeScript workspace resolved `esbuild` 0.25.10 transitively through
    the SDK toolchain. `esbuild` 0.28.1 adds integrity verification to the
    Deno binary download path addressed by
    [GHSA-gv7w-rqvm-qjhr](https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr),
    preventing an attacker-controlled npm registry from supplying an
    executable without a content check.
    
    ## What changed
    
    - Add a root workspace resolution for `esbuild` 0.28.1.
    - Regenerate `pnpm-lock.yaml` so `tsup`, `bundle-require`, and `ts-jest`
    all resolve the patched version.
    
    ## Validation
    
    - Frozen pnpm install, including the SDK's `tsup` build
    - `pnpm --filter @openai/codex-sdk exec jest tests/exec.test.ts
    --runInBand`
    - Confirmed the installed dependency graph contains only `esbuild`
    0.28.1
  • Upgrade bundled OpenSSL to 3.6.3 (#29487)
    ## Summary
    
    - upgrade the bundled OpenSSL source from 3.5.5 to 3.6.3
    - update the Bazel `openssl-sys` build dependency to use the upgraded
    source crate
    - refresh the Bazel module lockfile
    
    ## Why
    
    OpenSSL 3.5.5 is within the affected ranges for security issues fixed in
    later releases. The Rust `openssl-src` wrapper does not currently
    publish OpenSSL 3.5.7, so this moves the vendored Linux musl build to
    the available patched 3.6.3 release.
  • [codex] fetch featured IDs for remote plugins (#29485)
    ## Summary
    
    - fetch featured plugin IDs when the loaded catalog includes
    `openai-curated-remote`
    - extend the existing remote marketplace regression test to cover the
    featured IDs response
    
    ## Why
    
    When the remote plugin catalog was enabled, app-server loaded
    `openai-curated-remote` but skipped `/plugins/featured` because the
    request processor only fetched featured IDs for the local
    `openai-curated` marketplace. As a result, the desktop app could not
    render the backend-curated remote featured set.
    
    This keeps the existing local behavior and also returns the curated
    ranking for remote plugins.
    
    ## Test plan
    
    - `just fmt`
    - `git diff --check`
    - `just test -p codex-app-server
    plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
  • Register full CDP requirements feature (#28769)
    register cdp requirements feature flag
  • PAC 3 - Add Windows system proxy resolver (#26708)
    ## Summary
    
    Stacked on #26707.
    
    Adds the Windows implementation of the shared system-proxy contract.
    This allows Codex-owned auth clients to use the route Windows selects
    for each auth URL, including explicit PAC configuration, WPAD
    auto-detection, static proxies, and bypass rules.
    
    The `respect_system_proxy` feature is disabled by default, so existing
    client behavior remains unchanged unless explicitly enabled.
    
    ## Implementation
    
    - Adds Windows-only `codex-client` dependencies:
    - `windows-sys` with `Win32_Foundation` and `Win32_Networking_WinHttp`;
      - `sha2` for redacted cache keys.
    - Dispatches system-proxy resolution to `outbound_proxy/windows.rs` on
    Windows.
    - Reads the current-user WinHTTP/IE proxy configuration via
    `WinHttpGetIEProxyConfigForCurrentUser`.
    - Resolves explicit PAC URLs first, then OS-enabled WPAD auto-detection,
    then static proxy and bypass settings.
    - Uses `WinHttpGetProxyForUrl` for PAC/WPAD and maps results into the
    shared `SystemProxyDecision::{Direct, Proxy, Unavailable}` contract.
    - Parses `DIRECT`, `PROXY`, `HTTPS`, and keyed static proxy entries.
    - Treats unsupported schemes such as SOCKS as unavailable so the shared
    resolver can apply its environment-proxy fallback.
    - Handles Windows bypass entries, including `<local>` and host, suffix,
    wildcard, and port matching.
    - Releases WinHTTP-owned strings with `GlobalFree` and closes sessions
    with `WinHttpCloseHandle`.
    - Hashes URL-specific cache keys with SHA-256 so PAC decisions remain
    URL-specific without retaining raw request URLs or query strings.
    
    ## End-user behavior
    
    - Disabled/default: existing client behavior is unchanged.
    - Enabled with `[features.respect_system_proxy]`:
    - Windows auth clients honor explicit PAC configuration, OS-enabled
    WPAD, static proxies, and bypass rules;
      - valid OS/PAC `DIRECT` decisions use a direct connection;
    - unavailable system resolution falls back to explicit environment proxy
    variables, then `DIRECT`, through the shared contract from #26707.
    - Unsupported proxy schemes are not silently translated into a different
    route.
    - Custom CA handling remains separate from proxy selection.
    
    ## Tests
    
    Adds coverage for:
    
    - PAC-style proxy tokens such as `PROXY proxy.internal:8080` and `HTTPS
    proxy.internal:8443`;
    - static WinHTTP proxy entries keyed by target scheme;
    - `DIRECT` and unsupported proxy-token behavior;
    - Windows bypass matching, including `<local>`, wildcard, suffix, and
    port-qualified entries;
    - preserving URL-specific PAC cache decisions without retaining the raw
    URL on Windows.
  • [codex-core-plugins] Remote Plugin ID Persisted to File (#27669)
    ## This PR
    
    Remote plugin analytics cannot rely only on the in-memory
    installed-plugin snapshot because that snapshot is refreshed
    asynchronously after startup. This PR persists the authoritative backend
    identity alongside each cached remote plugin bundle so later consumers
    can resolve it without a network request.
    
    ### Behavior
    
    - Store Codex-owned remote installation metadata in an atomic
    `.codex-remote-plugin-install.json` sidecar under the plugin cache root.
    - Use a versioned, snake_case schema:
    
      ```json
      {
        "schema_version": 1,
        "remote_plugin_id": "plugins~Plugin_..."
      }
      ```
    
    - Write the metadata during remote bundle installation.
    - Backfill it when bundle sync finds an already-current cached bundle.
    - Clear it when a generic/local install replaces the cache.
    - Let existing uninstall and stale-cache removal delete it with the
    plugin cache root.
    - Reject unsupported schema versions rather than silently misreading
    future formats.
    
    This PR does not change analytics serialization or event behavior.
    
    ### Review surface
    
    The implementation is limited to four `codex-core-plugins` files:
    
    - `store.rs`: owns the versioned sidecar read/write/remove lifecycle.
    - `remote_bundle.rs`: persists the backend ID after a remote bundle
    install.
    - `remote/remote_installed_plugin_sync.rs`: backfills metadata for an
    already-current cached bundle.
    - Tests cover the storage lifecycle and both remote write paths.
    
    ## Testing / Validation
    
    ### Automated
    
    - `just test -p codex-core-plugins` (268 tests passed)
    - `just fix -p codex-core-plugins` passes with one pre-existing
    `large_enum_variant` warning in `manifest.rs`.
    - Coverage verifies the exact filename and JSON schema, identity
    replacement, local reinstall clearing, uninstall cleanup, remote bundle
    installation, unsupported schema rejection, and installed-plugin sync
    backfill.
    
    ### Live manual validation
    
    Validated the production app-server RPC path with an isolated temporary
    `CODEX_HOME` and the PR-built Codex binary. The app-server communicated
    over stdio and did not bind a port.
    
    Test plugin: `plugins~Plugin_b80dd84519148191a409cde181c9b3d6`
    (`build-macos-apps@openai-curated-remote`).
    
    1. Confirmed `plugin/read` initially reported the plugin uninstalled.
    2. Installed it through `plugin/install` and confirmed version `0.1.4`
    was cached.
    3. Verified
    `$CODEX_HOME/plugins/cache/openai-curated-remote/build-macos-apps/.codex-remote-plugin-install.json`
    was created beside the `0.1.4/` bundle directory with mode `0600` and
    the expected contents:
    
       ```json
       {
         "schema_version": 1,
    "remote_plugin_id": "plugins~Plugin_b80dd84519148191a409cde181c9b3d6"
       }
       ```
    
    4. Deleted only the sidecar, restarted the app-server, and confirmed
    installed-plugin startup sync recreated it with the same contents.
    5. Uninstalled through `plugin/uninstall`, confirmed `plugin/read`
    returned `installed: false`, and verified the local plugin cache root
    was removed.
    6. Restored the account's original uninstalled state and removed the
    isolated home and copied credentials.
    
    ## 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          merged
        └── #27669  Persist remote plugin identity           ← this PR
    
    Next:
    └── Final PR: add explicit local and remote IDs to plugin analytics
    ```
    
    This PR is based directly on `main`; prerequisite
    [#27102](https://github.com/openai/codex/pull/27102) has merged. The
    original combined [#26281](https://github.com/openai/codex/pull/26281)
    remains the aggregate reference until the final replacement PR is
    published.
  • chore: advance tungstenite fork pins (#29480)
    ## Why
    
    `openai-oss-forks/tokio-tungstenite` now includes the updated
    `tungstenite` fork revision from
    [openai-oss-forks/tokio-tungstenite#3](https://github.com/openai-oss-forks/tokio-tungstenite/pull/3).
    Codex should consume the merged fork commit and resolve its direct and
    transitive `tungstenite` dependencies to the same revision instead of
    retaining the older pins.
    
    ## What Changed
    
    - Advanced the `tokio-tungstenite` git pin to
    `0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186`.
    - Advanced the `tungstenite` fork pin to
    `4fffad30fe373adbdcffab9545e9e9bf4f2fc19f` and adjusted the patch source
    so the transitive dependency resolves to that revision.
    - Updated `Cargo.lock` and `MODULE.bazel.lock` to match the dependency
    graph.
  • Honor startup custom CA bundles with managed MITM (#29014)
    ## Why
    
    When Codex starts with a custom CA override such as
    `SSL_CERT_FILE=/path/to/corp-ca.pem codex`, `rustls-native-certs` treats
    that override as a replacement for the platform trust store. The managed
    proxy then rewrites child CA variables to its generated bundle, so the
    custom root or the ordinary platform roots can be lost. The proxy's
    upstream TLS connector must trust the same roots or private and
    corporate upstream certificates still fail after interception.
    
    ## What
    
    - load platform-native roots without consulting inherited CA override
    variables
    - append certificates from the existing curated startup CA file
    variables and `SSL_CERT_DIR`
    - share those platform and startup roots with the MITM upstream rustls
    connector
    - exclude the Codex managed MITM CA from upstream trust
    - normalize OpenSSL `TRUSTED CERTIFICATE` blocks while dropping trailing
    trust metadata
    - skip an inherited current Codex-managed bundle so nested launches do
    not duplicate it
    - append the Codex managed MITM CA to the child-facing bundle
    - copy certificate material only, so a private key or unrelated text
    colocated in a startup file is never exposed through the public bundle
    
    This is intentionally limited to CA paths present when Codex starts. It
    does not parse inline shell assignments or add per-command bundle
    materialization.
    
    This changes only `codex-network-proxy` and dependency metadata; it does
    not touch `codex-core` or sandbox orchestration.
    
    ## Validation
    
    - `just test -p codex-network-proxy`
    - includes an end-to-end upstream TLS test using a server trusted only
    by the startup custom CA
    - `just fix -p codex-network-proxy`
    - `just bazel-lock-check`
  • 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>
  • [codex] handle request_user_input in app-server test client (#29476)
    ## Why
    
    `codex-app-server-test-client` previously treated
    `item/tool/requestUserInput` as an unsupported server request and
    terminated the connection. That made it impossible to use the client for
    end-to-end testing of interactive turns: an operator could observe the
    request, but could not answer it and confirm that the same turn resumed.
    
    ## What changed
    
    - Handle `ToolRequestUserInput` server requests in the test client's
    central request dispatcher.
    - Render numbered terminal choices, accept exact option labels, support
    free-form `Other` and text-only questions, and collect multiple answers.
    - Send a protocol-native `ToolRequestUserInputResponse` and continue
    streaming the active turn.
    - Fail clearly when interactive input is requested without a terminal.
    - Document the interactive behavior and add focused tests for option
    selection, free-form answers, multiple questions, and invalid-selection
    retries.
    
    ## Testing
    
    - `just test -p codex-app-server-test-client`
    - `just bazel-lock-check`
    - Manually exercised the app-server flow, selected `TUI`, observed
    `serverRequest/resolved`, and verified that the same turn completed with
    the selected answer.
  • 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
  • Allow ChatGPT accounts without email (#28991)
    # Summary
    
    Codex required every ChatGPT account to have an email address. A
    service-account personal access token can return valid account metadata
    without one, so PAT login failed while decoding the metadata response.
    
    This change makes email optional in the account metadata type that owns
    it and preserves that absence through authentication, provider account
    state, the app-server API, generated clients, and TUI bootstrap.
    Existing accounts with email addresses keep the same behavior.
    
    ## Behavior-changing call sites
    
    | Call site | Behavior after this change |
    | --- | --- |
    | `login/src/auth/personal_access_token.rs` | PAT metadata accepts a
    missing or null email and retains `None`. |
    | `agent-identity/src/lib.rs` | Agent Identity JWT claims accept an
    omitted email. |
    | `login/src/auth/storage.rs` and `login/src/auth/agent_identity.rs` |
    Stored and managed Agent Identity records carry `Option<String>`.
    Deserialization maps the legacy empty-string sentinel to `None`. |
    | `login/src/auth/manager.rs` | `get_account_email` returns the stored
    option, and managed identity bootstrap no longer converts `None` to an
    empty string. |
    | `model-provider/src/provider.rs` and `protocol/src/account.rs` | A
    ChatGPT provider account requires a plan type but may carry no email. |
    | `app-server-protocol/src/protocol/v2/account.rs` | `account/read`
    keeps the `email` field on the wire and returns `null` when the account
    has no email. Generated TypeScript and JSON schemas describe a required,
    nullable field. |
    | `sdk/python/src/openai_codex/generated/v2_all.py` | The generated
    Python `ChatgptAccount` model accepts `None` for email. |
    | `tui/src/app_server_session.rs` | Email-less ChatGPT accounts
    bootstrap normally, keep external feedback routing, omit account-email
    telemetry, and display the plan in account status. |
    
    ## Design decisions
    
    - Missing email remains `None` at every layer. The code never uses an
    empty string as a substitute.
    - The app-server response includes `"email": null` instead of omitting
    the field. Clients retain a stable response shape.
    - Plan type remains required for provider account state. This change
    relaxes only the email assumption.
    
    ## Testing
    
    Tests: affected test targets compile, scoped Clippy and formatting pass,
    a focused TUI snapshot covers plan-only account status, real
    before/after PAT login smoke covers metadata without email, app-server
    smoke covers `account/read` with `email: null`, and a regression smoke
    covers an existing email-bearing PAT. Unit tests run in CI.
    
    ## Evidence
    
    Visual smoke evidence will be attached here.
  • 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.
  • [codex] Fix usage-limit reset copy and state (#28793)
    ## Why
    
    The reset flow introduced in #28154 still describes earned reset credits
    as "rate-limit resets" and uses generic reset-scope copy. It can also
    retain a stale available-credit count after redemption or an account
    change, leaving the reset action enabled after the last credit is used.
    
    This follow-up updates terminology only within that reset feature.
    Existing rate-limit wording elsewhere in the CLI and TUI is unchanged.
    
    ## What changed
    
    - Rename reset-specific `/usage` menu items, startup hints, and reset
    dialogs to "usage limit reset."
    - Describe monthly resets for Free, Go, and accounts that report a
    monthly usage window; otherwise describe the current 5-hour and weekly
    limits.
    - Recheck a cached zero balance when `/usage` is reopened, and refresh
    the balance after redemption so the final reset immediately disables the
    action.
    - Correlate async refresh results before updating snapshots and clear
    account-derived reset state, warnings, prompts, and status surfaces when
    the account changes.
    
    ## Validation
    
    - `just test -p codex-tui chatwidget::tests::usage` — 29 passed.
    - `just test -p codex-tui chatwidget::tests::status_command_tests` — 7
    passed.
    - Account-boundary prompt and plan-mode prompt regression tests passed.
    - `cargo insta pending-snapshots` from `codex-rs/tui` — no pending
    snapshots.\
    
    <img width="814" height="318" alt="image"
    src="https://github.com/user-attachments/assets/2a460e96-458b-4805-8d9f-c759382d21a4"
    />
    view for monthly
    <img width="905" height="243" alt="image"
    src="https://github.com/user-attachments/assets/179f88e3-08fb-4af5-8dc6-ce6a944ed681"
    />
  • [sdk/python] Stop advertising HTTP image URLs (#29464)
    ## Summary
    
    - use generated image data URLs in the Python SDK examples and notebook
    - document HTTP and HTTPS image URLs as deprecated and recommend
    `LocalImageInput`
    - replace the remote-URL integration test with data-URL coverage
    
    `ImageInput` remains available for data URLs. The SDK does not duplicate
    app-server URL validation.
    
    ## Testing
    
    - `uv run --frozen --no-sync ruff check --output-format=full .`
    - `uv run --frozen --no-sync ruff format --check .`
    - full Python SDK test suite with an isolated writable
    `CODEX_SQLITE_HOME` (119 passed, 38 skipped)
  • 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.
  • TUI Plugin Sharing 3 - render remote plugin catalog sections (#26703)
    ## Summary
    
    [#26701](https://github.com/openai/codex/pull/26701) added remote plugin
    identity support, [#26702](https://github.com/openai/codex/pull/26702)
    added remote-section fetching and state, and
    [#28768](https://github.com/openai/codex/pull/28768) extracted the
    catalog rendering module. This PR builds the product-facing `/plugins`
    catalog on that foundation so remote records appear as OpenAI Curated,
    Workspace, and Shared with me sections rather than backend marketplace
    implementation details.
    
    Plugin details remain read-only for sharing metadata. This PR does not
    add share-authoring actions or change the app-server protocol.
    
    ## Changes
    
    - Renders OpenAI Curated, Workspace, and Shared with me sections with
    loading, empty, and error states.
    - Preserves section selection and stable tab ordering as remote sections
    transition between fallback and populated states.
    - Shows OpenAI Curated loading only when the explicit vertical fallback
    request was issued.
    - Centralizes remote marketplace identity matching around the existing
    marketplace constants.
    - Uses product labels for remote marketplaces and identifies the
    personal marketplace as Local by its path.
    - Shows read-only source, authentication, version, and sharing metadata
    in plugin detail views.
    - Applies narrow display deduplication for local and remote records
    sharing a remote plugin ID:
      - installed records take precedence;
    - local mapped sources are preferred for details only when their
    installed state matches the selected record.
    - Returns from detail and confirmation views through the current plugin
    cache so newly loaded remote sections are not overwritten by an older
    captured response.
    - Keeps admin-disabled plugins view-only and labels default-installed
    plugins as Available by default.
    
    ## Tests
    
    New tests:
    
    - `plugins_popup_admin_disabled_available_plugin_has_view_only_hint`
    - `plugins_popup_remote_section_fallback_states_snapshot`
    -
    `plugins_popup_installed_remote_row_keeps_remote_detail_when_local_share_is_uninstalled`
    
    Updated existing plugin catalog tests and snapshots for product labels,
    detail metadata, personal-marketplace labeling, and stable tab ordering.
    
    Verification:
    
    - `cargo clippy -p codex-tui --all-targets -- -D warnings`
    
    ## Follow-ups
    
    - Local/remote duplicate normalization should eventually move into
    app-server. This PR intentionally keeps the compatibility behavior
    narrow and display-only.
    - PR5 will sanitize sensitive components before displaying Git source
    URLs.
  • [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.
  • ci: restore custom Windows runner with hermetic LLVM 0.7.9 (#29143)
    The custom Windows argument-comment-lint job was temporarily moved to
    `windows-2022` in #28940 after hermetic LLVM source extraction failed on
    the newer runner. This takes the upstream extraction fix so the job can
    return to the intended custom runner.
    
    This upgrades `llvm` to `0.7.9` and `rules_cc` to `0.2.18`, refreshes
    the module lock, rebases the remaining Windows and custom libc++
    patches, drops the obsolete symlink-extraction workaround, and restores
    the `windows-x64` runner configuration.
    
    Validation:
    
    - Verified all LLVM patches apply cleanly against the `0.7.9` source.
    - Built `@llvm-project//compiler-rt:clang_rt.builtins.static`.
  • 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
  • Filter noisy targets from persistent logs (#29457)
    ## Why
    
    The local SQLite log sink currently enables TRACE for every target. This
    persists high-volume dependency logs bridged through `target=log` and
    duplicates OpenTelemetry mirror events in `codex_otel.log_only` and
    `codex_otel.trace_safe`.
    
    These records rapidly consume the per-partition log budget and cause
    unnecessary SQLite insert-and-prune churn.
    
    ## What changed
    
    - Keep TRACE persistence for other targets.
    - Exclude bridged `target=log` events from the SQLite sink.
    - Exclude the two `codex_otel` mirror targets from the SQLite sink.
    - Share the same filter between app-server and TUI.
    
    Remote OpenTelemetry export and metrics are unchanged.