Commit Graph

800 Commits

  • Add realtime speech append control (#27917)
    ## Why
    
    Realtime voice harness tuning needs app-side control over what backend
    Codex text is spoken. Backend orchestrator text is written for a reading
    UI, so automatically speaking every preamble, progress update, or final
    assistant message can make the realtime voice model too chatty.
    
    For experimentation, clients need two simple controls: keep app/client
    text-item injection on the existing item-create path, and add an
    explicit speakable path that app code can call only when it wants
    realtime to speak. Automatic Codex output also needs an opt-in way to
    switch from the protocol's default speakable path to regular realtime
    items, with a caller-provided prefix so prompt wording can be tuned
    outside core.
    
    The default remains unchanged: if a client omits the new start fields
    and never calls `appendSpeech`, automatic backend output continues down
    the existing speakable path for the selected realtime protocol.
    
    ## What Changed
    
    - Adds experimental `thread/realtime/appendSpeech` for app-provided
    speakable text.
    - Keeps existing `thread/realtime/appendText` as the item-create API for
    app-provided realtime text items.
    - Adds `codexResponsesAsItems` / `codex_responses_as_items` on
    `thread/realtime/start` to send automatic Codex responses with
    `conversation.item.create` instead of the protocol's default speakable
    output path.
    - Adds `codexResponseItemPrefix` / `codex_response_item_prefix` so
    clients can prepend experiment instructions to those automatic Codex
    response items.
    - Keeps literal `conversation.handoff.append` routing scoped to the v1
    speakable path; v2 default speech uses its item/function-output plus
    `response.create` behavior.
    - Removes the earlier public silent-context API and hardcoded
    silent-context prefix.
    - Updates realtime tests to cover default automatic speakable behavior,
    opt-in automatic item-create behavior, and explicit `appendSpeech`
    behavior.
    
    ## Validation
    
    - `cargo check -p codex-core -p codex-app-server -p codex-api`
    - `just test -p codex-app-server realtime_conversation`
    - `just test -p codex-core realtime_conversation` (50/51 passed in the
    filtered parallel run; the lone failure passed when rerun in isolation)
    - `just test -p codex-core
    conversation_mirrors_assistant_message_text_to_realtime_handoff`
    - `just test -p codex-api
    e2e_connect_and_exchange_events_against_mock_ws_server`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
    - `cargo build -p codex-cli`
  • [codex] Add created-by-me remote plugin marketplace (#28203)
    ## Summary
    - add the `created-by-me-remote` marketplace backed by paginated
    `scope=USER` plugin directory and installed-plugin requests
    - include USER plugins in installed-plugin caching, bundle sync, and
    stale-cache cleanup without client-side discoverability filtering
    - expose the marketplace through app-server v2 and regenerate the
    protocol schemas
    
    ## Testing
    - `cargo build -p codex-app-server --bin codex-app-server`
    - production-auth `plugin/list` smoke test for `created-by-me-remote`
    (returned the expected USER plugin as installed and enabled)
    - `just test -p codex-core-plugins` (221 passed)
    - `just test -p codex-app-server-protocol` (231 passed)
    - `just test -p codex-app-server suite::v2::plugin_list::` (37 passed)
    - `just fix -p codex-core-plugins -p codex-app-server-protocol -p
    codex-app-server`
    - `just fmt`
  • feat(core): add metadata field to ResponseItem (#28355)
    ## Description
    
    This PR adds an optional `metadata` field to `ResponseItem` for
    Responses API calls. Only mechanical plumbing, no actual values
    populated and sent yet. Turns out just adding a new field to
    `ResponseItem` has quite a large blast radius already.
    
    This change is backwards compatible because `metadata` is optional and
    omitted when absent, so existing response items and rollout history
    without it still deserialize and requests that do not set it keep the
    same wire shape. For provider compatibility, we strip out `metadata`
    before non-OpenAI Responses requests so Azure and AWS Bedrock never see
    this field.
    
    My followup PR here will actually make use of it to start storing and
    passing along `turn_id`: https://github.com/openai/codex/pull/28360
    
    ## What changed
    
    - Added `ResponseItemMetadata` with optional `turn_id`, plus optional
    `metadata` on Responses API item variants and inter-agent communication.
    - Preserved item metadata through response-item rewrites such as
    truncation, missing tool-output synthesis, compaction history
    rebuilding, visible-history conversion, rollout/resume, and generated
    app-server schemas/types.
    - Strip item metadata from non-OpenAI Responses requests while
    preserving it for OpenAI-shaped requests.
    - Updated the mechanical fixture/test construction churn required by the
    new optional field.
  • feat(app-server): expose rate-limit reset credits (#28143)
    ## Why
    
    Codex users can earn personal rate-limit reset credits, but app-server
    clients do not currently have an API for reading or redeeming them. This
    adds the backend and protocol foundation used by the `/usage` TUI flow
    in #28154.
    
    ## What changed
    
    - Extend `account/rateLimits/read` with a nullable
    `rateLimitResetCredits` summary sourced from the existing usage
    response.
    - Add backend-client and app-server support for consuming a reset with a
    caller-generated idempotency key. A UUID is recommended, and clients
    reuse the same key when retrying the same logical reset.
    - Return only the consume `outcome`; clients refetch
    `account/rateLimits/read` for updated window state.
    - Document the response field and each consume outcome, and regenerate
    the JSON and TypeScript schema fixtures.
    - Clarify in `AGENTS.md` that new app-server string enum values use
    camelCase on the wire.
    - Update the existing TUI response fixture for the expanded protocol
    shape.
    - Add coverage for authentication, response mapping, backend failures,
    consume outcomes, and request timeout behavior.
    
    ## Validation
    
    - `just test -p codex-app-server-protocol` — 231 passed.
    - `just test -p codex-backend-client` — 14 passed.
    - Focused `codex-app-server` reset-credit tests — 5 passed.
    - Focused `codex-tui` protocol response fixture test — passed.
    - `just fix -p codex-backend-client -p codex-app-server-protocol -p
    codex-app-server` — passed.
    - `just fmt` — passed.
  • [codex] Add external agent import result accounting (#28008)
    ## Why
    
    External-agent imports can complete synchronously or continue in the
    background for plugins/sessions. Clients need a stable import id to
    correlate the immediate response with the eventual completion
    notification, and the completion payload needs enough accounting to show
    which artifact types succeeded or failed without hiding partial
    failures.
    
    ## What Changed
    
    - `externalAgentConfig/import` now returns an `importId`;
    `externalAgentConfig/import/completed` includes the same `importId` plus
    type-level `itemResults`.
    - Completed `itemResults` report `successCount`, `errorCount`,
    `successes`, and `rawErrors` for each migrated item type.
    - Added protocol/schema/TypeScript types for import successes, raw
    errors, and type-level results. No progress notification is included in
    the final PR.
    - `ExternalAgentConfigService::import` now returns an outcome object
    with synchronous item results and pending plugin imports.
    - Plugin import outcomes track succeeded/failed marketplaces, plugin
    ids, and raw errors. Plugin failures can be reported in completed
    accounting while later migration items continue.
    - Non-plugin synchronous import failures still fail the request, so
    invalid config/skills-style failures are not reported as a successful
    import response.
    - Session imports now return item results. Successful imports include
    the source session path and imported thread id; prepare, persist,
    ledger, and source-validation failures become raw errors in completion
    accounting where the import can continue.
    - The request processor generates the `importId`, aggregates synchronous
    results with background plugin/session results, and sends a single
    completed notification when all selected work is done.
    - App-server docs and generated schema fixtures were updated for the new
    response/completed payload shapes.
    
    ## Validation
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server-client event_requires_delivery`
    - `CODEX_SQLITE_HOME=/private/tmp/codex-app-server-review-sync-error
    just test -p codex-app-server
    external_agent_config_import_returns_error_for_failed_sync_import`
    - `CODEX_SQLITE_HOME=/private/tmp/codex-app-server-review-external-agent
    just test -p codex-app-server external_agent_config`
    
    Note: local sandbox validation used `CODEX_SQLITE_HOME` because the
    default sqlite state path is read-only in this environment.
  • skills: cache orchestrator resources per thread (#28336)
    ## Why
    
    Hosted orchestrator skills are read through the remote MCP resource
    server. Within one thread, the same catalog or skill resource can be
    requested multiple times by prompt injection and the `skills.list` /
    `skills.read` tools. Re-fetching adds latency and can make those
    surfaces observe different remote contents during the same thread.
    
    This is a follow-up to #28333: orchestrator skills remain limited to
    threads without a local executor, and those threads now get a stable
    per-thread view of the remote skill data they use.
    
    ## What changed
    
    - Reuse the existing per-thread orchestrator catalog snapshot for
    `skills.list` and `skills.read` availability checks.
    - Cache successful orchestrator resource reads by authority, package,
    and resource so prompt injection and tool calls share the same contents.
    - Keep the cache memory-only and bounded to 100 resources and 8 MiB per
    thread.
    - Leave host and executor skill reads unchanged, and do not cache failed
    remote reads.
    
    ## Verification
    
    - Extended the app-server MCP resource integration test to read the same
    hosted skill resource twice and verify that the remote server receives
    one read.
    - The same test verifies that catalog discovery and the selected skill's
    main prompt are each fetched only once per thread.
  • Support staging OAuth client ID overrides (#28257)
    ## Summary
    
    - allow app-server ChatGPT login to use a configured OAuth client ID
    - reuse the same client ID for refresh and revoke requests
    - cover staging login, refresh, and revoke request payloads
    
    ## Tests
    
    - `just test -p codex-login`
    - `just test -p codex-app-server
    login_account_chatgpt_uses_debug_oauth_overrides`
    - `just test -p codex-login
    logout_with_revoke_revokes_refresh_token_then_removes_auth`
    - `just fix -p codex-login`
    - `just fix -p codex-app-server`
    - `just fmt`
  • TUI Plugin Sharing 2 - add remote plugin section plumbing (#26702)
    This adds the background plumbing for remote-backed plugin catalog
    sections while leaving the fuller directory presentation to the next PR.
    The TUI can fetch section-specific remote marketplace results, keep
    local plugin data available, and carry section errors forward for later
    rendering.
    
    - Fetches explicit remote marketplace kinds for curated, workspace, and
    shared-with-me sections.
    - Gates shared-with-me loading on the plugin sharing feature flag.
    - Adds section-level error state and user-actionable error copy.
    - Merges remote marketplace results into the cached plugin list without
    discarding local results.
  • [codex] preserve explicit environment cwd (#27995)
    ## Why
    
    `TurnEnvironmentSelections::new` rewrote the primary environment's
    explicit `cwd` to the legacy fallback cwd. For a remote-first selection,
    this could replace the remote working directory with a local fallback
    path and made the legacy cwd overlay authoritative over
    environment-owned state.
    
    ## What changed
    
    - Preserve every explicit environment cwd when constructing turn
    environment selections.
    - Keep `cwd`-only app-server updates compatible by rebuilding the
    default environment selections at the requested cwd.
    - Cover both explicit primary cwd preservation and cwd-only updates
    reaching the model-visible execution environment.
    
    ## Testing
    
    - `just test -p codex-core
    session_update_settings_does_not_rewrite_sticky_environment_cwds`
    - `just test -p codex-core
    environment_settings_preserve_explicit_primary_cwd`
    - `just test -p codex-app-server
    thread_settings_update_cwd_retargets_default_environment`
  • Expose explicit dynamic tool namespaces in thread start (#27371)
    Stacked on #27365.
    
    ## Stack note
    
    [#27365](https://github.com/openai/codex/pull/27365) kept `thread/start`
    unchanged and converted its input in `thread_processor`. This PR updates
    `thread/start` to accept explicit functions and namespaces directly.
    
    Legacy per-tool arrays are still accepted and converted while reading
    the request. As a result, `thread_processor` can validate and pass the
    tools through directly, which is why some code added in #27365 is
    removed here.
    
    ## Why
    
    `thread/start.dynamicTools` still repeats namespace data on each
    function even though core now stores explicit namespace groups. The
    request API should use the same shape so each namespace has one
    description and one member list.
    
    ## What changed
    
    - Accept top-level functions and explicit namespace objects in
    `dynamicTools`.
    - Continue accepting fully legacy flat arrays, including
    `exposeToContext`.
    - Reject arrays that mix legacy and canonical entries.
    - Reuse the protocol types directly and remove the temporary app-server
    adapter.
    - Update validation, docs, the test client, and generated schemas.
    
    ## Test plan
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    dynamic_tool_call_round_trip_sends_text_content_items_to_model`
    - `just test -p codex-app-server
    thread_start_normalizes_legacy_dynamic_tools_into_model_request`
    - `just test -p codex-app-server
    thread_start_rejects_mixed_dynamic_tool_formats`
    - `just test -p codex-app-server
    thread_start_rejects_hidden_dynamic_tools_without_namespace`
  • skills: hide orchestrator skills with a local executor (#28333)
    ## Why
    
    App-server threads without a local executor need orchestrator-owned
    skills from the hosted `codex_apps` MCP server. Threads with the local
    executor already discover installed skills from the local filesystem.
    
    After the orchestrator skill provider was enabled for every app-server
    thread, local-executor threads also received the hosted skill catalog
    and the `skills.list` and `skills.read` tools. This changed the existing
    local behavior and could expose a second hosted copy of a skill that was
    already installed locally.
    
    ## What changed
    
    - Expose the thread's selected execution environments to extensions at
    thread startup.
    - Enable orchestrator skills only when the reserved local environment is
    not selected.
    - Apply that decision consistently to hosted skill catalog discovery,
    explicit skill injection, and the `skills.list` and `skills.read` tools.
    
    ## Verification
    
    - The existing no-executor app-server test continues to verify hosted
    skill discovery, invocation, and child-resource reads.
    - A new app-server test verifies that local-executor threads do not
    receive hosted skill context or `skills.*` tools.
  • Represent dynamic tools with explicit namespaces internally (#27365)
    Follow-up to #27356.
    
    ## Stack note
    
    This PR changes Codex's internal dynamic-tool shape while leaving
    `thread/start` unchanged. App-server therefore converts the existing
    per-tool input into explicit functions and namespaces before passing it
    to core.
    
    [#27371](https://github.com/openai/codex/pull/27371) updates
    `thread/start` to use the same explicit shape and removes this temporary
    conversion.
    
    ## Why
    
    Dynamic tools repeat namespace metadata on every function. Core should
    keep one explicit namespace with its member tools so descriptions and
    membership stay consistent across sessions and runtime planning.
    
    ## What changed
    
    - Represent dynamic tools as top-level functions or explicit namespaces
    in protocol and session state.
    - Read old flat rollout metadata and write the canonical hierarchy.
    - Flatten namespace members only when registering callable tools.
    - Keep `thread/start.dynamicTools` flat for now and normalize it at the
    app-server boundary.
    
    New builds can read old rollout metadata. Older builds cannot read newly
    written hierarchical metadata.
    
    ## Test plan
    
    - `just test -p codex-app-server
    thread_start_normalizes_legacy_dynamic_tools_into_model_request`
    - `just test -p codex-protocol
    session_meta_normalizes_legacy_dynamic_tools`
    - `just test -p codex-core
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
    - `just test -p codex-core
    tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call`
    - `just test -p codex-core code_mode_can_call_hidden_dynamic_tools`
    - `just test -p codex-tools`
  • Activate selected executor plugin MCPs in app-server (#27893)
    ## Why
    
    #27870 teaches the MCP extension how to discover stdio MCP servers
    declared by a selected executor plugin, but app-server does not yet
    install that contributor or initialize its per-thread state. As a
    result, `thread/start.selectedCapabilityRoots` can select the plugin
    while its MCP servers remain inactive.
    
    This PR closes that app-server wiring gap:
    
    ```text
    thread/start(selectedCapabilityRoots)
        -> initialize the thread's selected-plugin MCP snapshot
        -> read the selected plugin's .mcp.json through its environment
        -> start declared stdio servers in that environment
        -> expose their tools only on the selected thread
    ```
    
    ## What changed
    
    - Install the selected-executor-plugin MCP contributor in app-server
    using the existing shared `EnvironmentManager`.
    - Initialize its frozen thread snapshot when `thread/start` includes
    selected capability roots.
    - Document that selected plugin stdio MCPs are activated in their owning
    environment.
    - Add an app-server E2E covering the complete selection-to-tool-call
    path.
    
    The E2E verifies that:
    
    - the selected MCP process receives an executor-only environment value,
    proving the tool runs through the selected environment;
    - the MCP tool is advertised to the model and can be called;
    - a normal MCP config reload does not discard the thread's frozen
    selected-plugin registration;
    - another thread without the selected root does not see the MCP server.
    
    ## Scope
    
    - Existing sessions without `selectedCapabilityRoots` are unchanged.
    - Only stdio MCP declarations are activated. HTTP declarations remain
    inactive.
    - This does not change selected-root persistence across resume/fork or
    add hosted-plugin behavior.
    
    ## Verification
    
    - Focused app-server E2E:
    `selected_executor_plugin_exposes_its_stdio_mcp_only_to_that_thread`
    
    ## Stack
    
    Stacked on #27870.
  • [codex] Skip plugin MCP OAuth for matching app routes (#27461)
    ## Context
    
    This is PR5 in the plugin auth-routing stack. Earlier PRs make plugin
    surface projection auth-aware, narrow App/MCP conflicts by App
    declaration name, and keep connector listings auth-aware. This PR
    applies the same name-based App/MCP conflict rule into plugin MCP
    loading, so install-time MCP OAuth and plugin detail metadata both
    reflect the MCPs available for the current auth route.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Make `load_plugin_mcp_servers` auth-aware and let it load App
    declarations before filtering same-name MCP servers for Codex-backend
    auth.
    - Use that filtered MCP list for both install-time MCP OAuth and
    marketplace plugin detail metadata.
    - Preserve API-key/direct auth behavior so plugin MCP servers remain
    visible and can still start OAuth.
    
    ## Validation
    
    ```bash
    cargo fmt --all
    cargo test -p codex-core-plugins read_plugin_for_config_filters_mcp_servers_for_codex_backend_auth
    cargo check -p codex-core-plugins -p codex-app-server
    git diff --check
    git diff --cached --check
    ```
  • [codex] Preserve plugin apps in connector listings (#27602)
    ## Context
    
    This is PR4 in the plugin auth-routing stack. The earlier PRs make
    plugin surface projection auth-aware and narrow App/MCP conflicts by App
    declaration name. This PR keeps connector listing paths aligned with
    that projected plugin App set.
    
    This means ChatGPT/SIWC users will still see plugin-provided Apps in
    connector listing surfaces like the Apps/connector picker, while API-key
    users will not see Apps they cannot use.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Have app-server compute effective plugin Apps from the existing
    PluginsManager and pass them into connector listing.
    - Keep plugin Apps visible in Apps/connector listing for ChatGPT/SIWC
    users.
    - Keep API-key-style auth from surfacing plugin Apps in connector
    listings.
    
    ## Validation
    
    ```bash
    cargo test -p codex-chatgpt connectors::tests
    cargo test -p codex-app-server list_apps_includes_plugin_apps_for_chatgpt_auth
    git diff --check
    ```
  • feat(app-server): filter threads by parent (#26662)
    ## Why
    
    Clients that display or coordinate spawned subagents need an
    authoritative snapshot of a thread's immediate spawned children when
    they connect to app-server or recover after missing live events.
    `thread/list` cannot query by parent, so clients must otherwise scan
    unrelated threads or reconstruct relationships from rollout history and
    transient events.
    
    The direct spawn relationship already exists in persisted
    `thread_spawn_edges` state. Review and Guardian threads do not
    participate in that lifecycle and are intentionally outside this
    filter's scope.
    
    ## What changed
    
    This adds an experimental `parentThreadId` filter to `thread/list`.
    Parent-filtered requests return direct spawned children from persisted
    state while preserving the existing response shape, explicit filters,
    sorting, and timestamp-only cursor behavior. The lookup does not read
    rollout transcripts or recursively return descendants.
    
    Supersedes #25112 with the narrower `thread/list` filter approach.
    
    ## How it works
    
    1. An experimental client passes a valid thread ID as `parentThreadId`.
    2. App-server routes the list through the existing thread-store and
    state-database boundaries.
    3. SQLite selects threads whose IDs have a direct persisted spawn edge
    from that parent.
    4. Omitted provider and source filters include all values; explicit
    filters keep ordinary `thread/list` semantics.
    5. Grandchildren, Review threads, and Guardian threads are excluded.
    
    ## Verification
    
    State (144 tests), rollout (69 tests), and focused app-server
    thread-list (31 tests) suites passed. Scoped Clippy checks and
    repository formatting also passed. Coverage includes direct spawned
    children, omitted grandchildren, pagination, malformed IDs, mixed source
    kinds, explicit filters, and operation without rollout files.
  • build: run buildifier from just fmt (#28125)
    ## Intent
    
    Keep Bazel and Starlark files consistently formatted without requiring
    contributors to install or version buildifier themselves.
    
    ## Implementation
    
    - Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
    v8.5.1.
    - Run buildifier from the shared `just fmt` and `just fmt-check` driver,
    with Windows-safe explicit DotSlash invocation.
    - Provision DotSlash in formatting CI and contributor devcontainers, and
    document the source-build prerequisite.
    - Apply the initial mechanical buildifier formatting baseline.
  • feat(app-server): enforce managed remote control disable (#27961)
    ## Why
    
    Managed deployments need a reliable deny gate for remote control.
    Persisted enablement and explicit startup requests currently remain able
    to start the transport, while the removed `features.remote_control` key
    is intentionally only a compatibility no-op.
    
    This adds a dedicated requirement that administrators can use to force
    remote control off without deleting the user's persisted preference.
    Removing the requirement and restarting restores the prior choice.
    
    ## What Changed
    
    - Added top-level `allow_remote_control` requirements parsing, sourced
    layer precedence, debug output, and `configRequirements/read` exposure
    as `allowRemoteControl`.
    - Added a typed transport policy captured from the startup requirements
    snapshot. Managed disable forces the initial state to disabled and
    prevents enrollment, refresh, connection, and persisted-preference
    mutation.
    - Rejected every `remoteControl/*` RPC before parameter deserialization
    with JSON-RPC `-32600` and `remote control is disabled by managed
    requirements`.
    - Preserved the existing disabled status notification and the previous
    behavior when the requirement is `true` or omitted.
    - Regenerated app-server protocol schemas and documented the new
    requirement.
    
    ## Verification
    
    - Confirmed all remote-control RPCs, including a malformed request,
    return the managed-policy error while the initial status notification
    remains `disabled`.
    - Confirmed explicit ephemeral startup and persisted enablement make no
    backend connection and leave the SQLite preference unchanged.
    - Confirmed `allow_remote_control = true` does not enable or block
    remote control and `configRequirements/read` returns
    `allowRemoteControl: false` for the deny policy.
    
    Related issue: N/A (managed-policy hardening).
  • [codex] add roles to realtime append text (#27936)
    ## Summary
    
    Add an explicit `user` or `developer` role to
    `thread/realtime/appendText` and propagate it through the realtime input
    queue into `conversation.item.create`. Older JSON clients that omit the
    field continue to default to `user`.
    
    This lets app-provided context such as memory retain developer authority
    without bypassing app-server through a renderer-owned data channel. The
    app-server schemas, API documentation, and focused protocol and
    websocket coverage are updated with the new contract.
    
    The Codex Apps consumer is tracked in
    [openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • Handle standalone image generation failures as terminal items (#27920)
    ## Why
    
    Standalone image generation emitted a started item but no terminal item
    when the backend failed. Clients could leave the operation unresolved or
    render it as successful.
    
    ## What changed
    
    - Emit a terminal image-generation item with `status: "failed"` when
    generation or editing fails.
    - Skip image persistence for failed terminal items.
    - Render failed image generation distinctly in TUI history.
    - Preserve the status when handling live and replayed terminal items.
    
    ## Looks for TUI, App-Side change needed 
    
    <img width="867" height="89" alt="image"
    src="https://github.com/user-attachments/assets/9e32342f-a982-411e-8498-456639fc468a"
    />
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - App-server image-generation tests
    - Core stream-event tests
    - TUI image-generation lifecycle and snapshot tests
    - Scoped Clippy and formatting
  • [codex] expose remote plugin share URL (#27890)
    ## Summary
    
    - expose the remote plugin detail endpoint's `share_url` as nullable
    `PluginDetail.shareUrl`
    - preserve existing `PluginSummary.shareContext` behavior for local and
    workspace sharing flows
    - regenerate the app-server TypeScript and JSON schema fixtures
    
    ## Why
    
    The remote plugin detail response already includes a canonical
    `share_url`, but that value was not surfaced by `plugin/read` for global
    plugins. Global plugins intentionally have no `shareContext`, so using
    that model for the URL would change the semantics consumed by the
    existing share modal.
    
    ## User impact
    
    Codex clients can use `PluginDetail.shareUrl` for a remote plugin's
    copy-link action, including when the plugin is disabled by an
    administrator, without changing existing share-modal or ownership
    behavior.
    
    ## Validation
    
    - `cargo test -p codex-app-server
    plugin_read_includes_share_url_for_admin_disabled_remote_plugin`
    - `cargo test -p codex-app-server-protocol
    typescript_schema_fixtures_match_generated`
    - `cargo test -p codex-app-server-protocol
    json_schema_fixtures_match_generated`
    - `cargo fmt --all`
  • realtime: add AVAS architecture override (#27720)
    ## Summary
    
    Adds a `RealtimeConversationArchitecture` option for realtime
    conversation startup, with `realtimeapi` as the default and `avas` as an
    opt-in architecture.
    
    The AVAS path is limited to realtime v1 conversational WebRTC starts,
    and WebRTC call creation appends `intent=quicksilver&architecture=avas`
    to `/v1/realtime/calls`. The existing sideband websocket still joins by
    `call_id`.
    
    This also exposes the per-session architecture override through
    app-server v2 `thread/realtime/start` params and updates the config
    schema for `[realtime].architecture`.
    
    ## Validation
    
    - `just fmt`
    - `just write-config-schema`
    - `just test -p codex-api sends_avas_session_call_query_params`
    - `just test -p codex-core -E
    'test(~conversation_webrtc_start_uses_avas_architecture_query)'`
    - `just test -p codex-core -E 'test(realtime_loads_from_config_toml)'`
    - `just test -p codex-app-server-protocol -E
    'test(~serialize_thread_realtime_start) |
    test(generated_ts_optional_nullable_fields_only_in_params)'`
    - `just test -p codex-app-server -E
    'test(realtime_webrtc_start_emits_sdp_notification)'`
  • [ez][codex-rs] Support approvals reviewer in app defaults (#27075)
    [from codex]
    
    ## Summary
    
    - add `approvals_reviewer` support to `[apps._default]`
    - resolve connected-app reviewers in per-app, app-default, then global
    order
    - expose the setting through the v2 config API and regenerate schema
    fixtures
    
    ## Context
    
    PR #25167 added `apps.<connector_id>.approvals_reviewer`, but the shared
    app defaults table could not specify the reviewer. This extends the same
    behavior to `[apps._default]` while preserving per-app overrides.
    
    Managed `allowed_approvals_reviewers` requirements still constrain both
    default and per-app values. A disallowed app value falls back to the
    global reviewer, and non-app MCP servers continue using the global
    reviewer.
    
    ## Testing
    
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just fmt`
    - `just test -p codex-config`
    - `just test -p codex-core app_approvals_reviewer`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server config_read_includes_apps`
  • Add request_user_input auto-resolution window contract (#27256)
    ## Why
    
    `request_user_input` is moving beyond its original plan-mode-only
    workflow, and future default/goal-mode usage needs a way for the model
    to ask helpful but non-blocking questions without forcing the turn to
    wait forever. This PR adds an explicit `autoResolutionMs` contract so a
    later client/runtime change can auto-resolve unanswered prompts after a
    bounded window while leaving truly blocking questions unchanged.
    
    This is contract plumbing only; it does not implement the client-side
    timer or auto-selection behavior, and the model-facing description
    treats the field as reserved unless the current runtime explicitly
    supports auto-resolution.
    
    ## What Changed
    
    - Added optional `autoResolutionMs` to the model-facing
    `request_user_input` args and core `RequestUserInputEvent`.
    - Added model-facing schema text for `autoResolutionMs` while marking it
    reserved for runtimes that explicitly support auto-resolution.
    - Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
    normalization by clamping out-of-range model-provided values.
    - Propagated the field through app-server v2
    `ToolRequestUserInputParams`, app-server request forwarding, generated
    TypeScript, and JSON schema fixtures.
    - Updated app-server, core, protocol, and TUI call sites/tests so
    omitted values preserve existing `None`/`null` behavior and coverage
    verifies a `Some(60_000)` round trip.
    
    ## Verification
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core request_user_input`
    - `just test -p codex-app-server request_user_input_round_trip`
    - `just test -p codex-tui request_user_input`
    - `just test -p codex-protocol`
  • feat(app-server): persist remote-control desired state (#27445)
    ## Why
    
    Remote-control runtime enablement and persisted enrollment preference
    were represented by separate flags. That made startup rehydration, RPC
    persistence, and new-enrollment seeding race with one another, and it
    did not cleanly distinguish runtime-only CLI or daemon starts from
    durable app-server RPC changes.
    
    ## What Changed
    
    - Replace the parallel enablement, seed, and rehydration flags with one
    transport-owned `RemoteControlDesiredState`.
    - Add nullable enrollment-scoped persistence and preserve existing
    preferences during enrollment upserts.
    - Rehydrate plain startup only after auth and client scope resolve,
    without overwriting a concurrent RPC transition.
    - Make ordinary `remoteControl/enable` and `remoteControl/disable`
    durable while retaining `ephemeral: true` for runtime-only callers.
    - Have the daemon explicitly request ephemeral enablement and regenerate
    the app-server schemas.
    
    ## Verification
    
    - Covered migration and `NULL`/`0`/`1` persistence round trips.
    - Covered plain-start rehydration and runtime-only versus durable
    enrollment seeding.
    - Covered durable enable, durable disable, and ephemeral enable through
    app-server RPC.
    - Covered the daemon's exact `{ "ephemeral": true }` request payload.
    
    Related issue: N/A (internal remote-control persistence architecture
    change).
  • Route image extension reads through turn environments v2 (#27498)
    ## Why
    
    Image generation used `std::fs::read` for referenced image paths, which
    did not support environment-backed filesystems or their sandbox context.
    
    ## What changed
    
    - Expose optional turn environments to extension tool calls.
    - Include each environment’s ID, working directory, filesystem, and
    sandbox context.
    - Read referenced images through the selected environment filesystem.
    - Keep sandbox usage at the extension call site so extensions can choose
    the appropriate access mode.
    - Consolidate image request construction into one async function.
    - Add coverage for successful environment reads and read failures.
    
    ## Validation
    
    - `cargo check -p codex-image-generation-extension --tests`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    `just test -p codex-image-generation-extension` could not complete
    because the build exhausted available disk space.
  • core: Consolidate Responses API Codex metadata (#27122)
    ## What
    Introduce a `CodexResponsesMetadata` struct that defines all the core
    metadata we send to Responses API. Example fields are `thread_id`,
    `turn_id`, `window_id`, etc.
    
    Going forward, `client_metadata["x-codex-turn-metadata"]` will be the
    canonical way Codex sends metadata to Responses API across both HTTP and
    websocket transports.
    
    For now, we continue to emit the existing top-level HTTP headers and
    top-level `client_metadata` fields from the same
    `CodexResponsesMetadata` struct for compatibility reasons.
    
    Also, app-server clients who specify additional
    `responsesapi_client_metadata` via `turn/start` and `turn/steer` will
    have those fields merged into
    `client_metadata["x-codex-turn-metadata"]`, but cannot override the
    reserved fields that core uses (i.e. the fields in
    `CodexResponsesMetadata`).
    
    ## Why
    
    Responses API request instrumentation is the source of truth for
    downstream Codex analytics that join requests by Codex IDs such as
    session, thread, turn, and context window. Before this change, those
    values were assembled through several request-specific paths: HTTP
    request bodies, websocket handshake headers, websocket `response.create`
    payloads, compaction requests, and the rich `x-codex-turn-metadata`
    envelope all had their own wiring.
    
    That made metadata propagation easy to drift across API-key/direct
    Responses API requests, ChatGPT-auth/proxied requests, websocket
    requests, and compaction requests. It also made additions like
    `window_id` error-prone because a field could be added to one transport
    projection but missed in another.
    
    ## What changed
    
    - Added `CodexResponsesMetadata` as the core-owned snapshot for Codex
    metadata sent to ResponsesAPI.
    - Render `client_metadata["x-codex-turn-metadata"]`, flat
    `client_metadata` projections, and direct compatibility headers from
    that same snapshot.
    - Include the known Codex-owned fields in the turn metadata blob,
    including installation/session/thread/turn/window IDs, request kind,
    lineage, sandbox/workspace metadata, timing, and compaction details.
    - Treat app-server `responsesapi_client_metadata` as enrichment for the
    Codex turn metadata blob while preventing those extras from overriding
    Codex-owned fields.
    - Use the same metadata path for normal turns, websocket prewarm, local
    compaction, remote v1 compaction, and remote v2 compaction.
    - Keep websocket connection-only preconnect metadata separate so
    handshakes carry compatibility identity headers without inventing a fake
    turn metadata blob.
    
    ## Verification
    
    - `cargo check -p codex-core`
    - `just fix -p codex-core`
  • [codex] Load user instructions through an injected provider (#27101)
    ## Why
    
    We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
    make embedders responsible for supplying user-level instructions. This
    also ensures user instructions load when no primary environment is
    selected.
    
    ## What changed
    
    Stacked on #27415, which makes `codex exec` surface thread-scoped
    runtime warnings.
    
    - Added `UserInstructionsProvider` to `codex-extension-api`, with
    absolute source attribution and recoverable loading warnings.
    - Added `codex-home` with the filesystem-backed provider for
    `AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
    trimming, lossy UTF-8 handling, and the existing uncapped global
    instruction size.
    - Removed global instruction loading from `Config` and require
    `ThreadManager` callers to inject a provider.
    - Load provider instructions once for each fresh root runtime, including
    runtimes without a primary environment. Running sessions retain their
    snapshot, while child agents inherit the parent snapshot without
    invoking the provider.
    - Keep provider instructions separate while loading project `AGENTS.md`,
    then assemble the model-visible instructions with the existing ordering,
    source attribution, warning, and turn-context behavior.
    - Wired the Codex home provider through the CLI, app server, MCP server,
    core facade, and thread-manager sample.
    
    ## Validation
    
    - `just test -p codex-home -p codex-extension-api`
    - `just test -p codex-core agents_md`
    - `just test -p codex-core guardian`
    - `just test -p codex-app-server
    thread_start_without_selected_environment_includes_only_global_instruction_source`
    - `just test -p codex-exec warning`
    - `just bazel-lock-check`
  • feat: disable orchestrator skills for now (#27646)
    Temp disable orchestrator-only skills while waiting for the endpoint to
    be fixed
  • [codex] Propagate plugin app categories (#27420)
    ## What
    - Parse optional `.app.json` `category` overrides for plugin apps.
    - Add nullable `category` to `AppSummary` and `AppTemplateSummary` in
    the app-server protocol.
    - Fall back from `branding.category` to the first non-empty
    `app_metadata.categories` value when building app/template summaries.
    - Regenerate schema/type fixtures and update plugin read/install tests.
    
    ## Why
    The plugin details UI needs a normalized per-app category. Some apps
    only provide their default category in metadata, while others need a
    local `.app.json` override.
  • skills: render catalog locators by authority (#27591)
    ## Why
    
    Hosted skills introduced by #27388 use opaque `skill://` resource
    identifiers, but the skills catalog rendered every locator as a `file`
    and told the model that every skill body lived on disk. That can send
    the model toward filesystem tools for a resource that must instead be
    read through its owning authority.
    
    The catalog should describe how each source is accessed without changing
    the underlying discovery or invocation behavior.
    
    ## What changed
    
    - Render host skills as `file`, executor-owned skills as `environment
    resource`, orchestrator-owned skills as `orchestrator resource`, and
    custom-provider skills as `custom resource`.
    - Update the shared no-alias guidance to describe source locators rather
    than assuming every skill is stored on the host filesystem.
    - Direct orchestrator resources through `skills.list` and `skills.read`,
    and explicitly tell the model not to treat `skill://` identifiers as
    filesystem paths.
    - Preserve the existing filesystem and alias behavior for local skills.
    
    ## Scope
    
    This PR changes only model-visible catalog rendering and guidance. It
    does not change skill discovery, selection, prompt injection, provider
    routing, catalog caching or refresh behavior, resource validation, or
    the `skills.*` tool contract.
    
    ## Verification
    
    - Extended skills-extension coverage for host-file and executor-resource
    labels.
    - Extended the no-executor app-server flow to assert
    orchestrator-resource wording and non-filesystem guidance.
  • test: cover referenced backend skill reads without an executor (#27404)
    ## Why
    
    PR #27388 lets models read child resources referenced by backend plugin
    skills without an executor. The integration fixture should prove that
    real flow: the injected `SKILL.md` advertises a child `skill://`
    resource, and `skills.read` resolves that exact resource through the
    backend provider.
    
    This is stacked on #27388.
    
    ## What changed
    
    - Adds a child-resource link to the backend skill fixture and asserts
    that it reaches model context.
    - Tightens the end-to-end skills test around `skills.list` followed by
    `skills.read` for the referenced resource.
    - Splits the existing app-server `mcpResource/read` coverage into a
    focused test so the generic RPC path remains covered independently.
    
    ## Validation
    
    - Adds app-server integration coverage for both the referenced backend
    skill resource and the generic MCP resource read path.
  • skills: expose remote skill resource tools (#27388)
    ## Why
    
    PR #27387 makes backend plugin skills discoverable and invocable without
    an executor, but resources referenced by those skills still sit behind
    the generic MCP resource surface. The model needs a skills-owned API
    that preserves the provider authority and package boundary instead of
    treating remote resources like local files.
    
    This is stacked on #27387.
    
    ## What
    
    - Adds one `skills` namespace with bounded `list` and `read` tools for
    remote skill providers.
    - Revalidates `authority + package` against the live remote catalog on
    every read, then routes the opaque resource ID back through that
    provider.
    - Allows the backend provider to read canonical child `skill://`
    resources while rejecting cross-package, non-canonical, query, fragment,
    and traversal-shaped URIs.
    - Caps each serialized tool result at 8 KB. Lists are paginated; reads
    return an opaque continuation cursor.
    - Marks the JSON output as external context so memory generation can
    apply its normal suppression policy.
    - Deliberately does not add `skills.search`; that waits for a bounded
    plugin-service search contract.
    
    ## Tool contract
    
    Pseudo-Python matching the wire shape:
    
    ```python
    from typing import Literal, NotRequired, TypedDict
    
    
    class RemoteSkillAuthority(TypedDict):
        kind: Literal["remote"]
        id: str  # e.g. "codex_apps"
    
    
    class RemoteSkill(TypedDict):
        authority: RemoteSkillAuthority
        package: str  # opaque provider-owned package ID
        name: str
        description: str
        main_resource: str  # opaque provider-owned SKILL.md ID
    
    
    class SkillsListParams(TypedDict):
        cursor: NotRequired[str]
    
    
    class SkillsListResult(TypedDict):
        skills: list[RemoteSkill]
        next_cursor: str | None
        warnings: list[str]
        truncated: bool
    
    
    class SkillsReadParams(TypedDict):
        authority: RemoteSkillAuthority  # copied from skills.list
        package: str  # copied from skills.list
        resource: str  # provider-owned child resource ID
        cursor: NotRequired[str]  # copy next_cursor to continue
    
    
    class SkillsReadResult(TypedDict):
        resource: str
        contents: str
        next_cursor: str | None
        truncated: bool
    
    
    class Skills:
        def list(self, params: SkillsListParams) -> SkillsListResult: ...
        def read(self, params: SkillsReadParams) -> SkillsReadResult: ...
    ```
    
    There is one namespace for all remote skills, not one tool or MCP server
    per skill. No resource ID is converted into a filesystem path.
    
    ## Backend dependency
    
    `/ps/mcp` must support direct reads of child resources such as
    `skill://plugin_demo/deploy/references/deploy.md`. This PR implements
    and tests the Codex side of that contract; production child reads remain
    dependent on the corresponding plugin-service support. Search remains
    out of scope until that service exposes a bounded search/resource API.
    
    ## Validation
    
    - Added an app-server integration test covering `skills.list` followed
    by `skills.read` with no executor.
    - Ran `just fmt`.
    - Ran `just bazel-lock-update` and `just bazel-lock-check`.
    - Did not run Rust tests or Clippy locally, per request; CI will run
    them.
  • core: enable remote compaction v2 by default (#27573)
    ## Why
    
    Remote compaction v2 is ready to become the default for providers that
    already support remote compaction. Leaving it behind an
    under-development opt-in keeps eligible sessions on the legacy
    remote-compaction path.
    
    This does not broaden provider eligibility: OpenAI and Azure move to v2,
    while Bedrock and OSS providers retain their existing local-compaction
    behavior.
    
    ## What changed
    
    - Mark `remote_compaction_v2` stable and enable it by default.
    - Make tests that intentionally cover legacy remote compaction
    explicitly disable v2.
    - Update parity coverage so v2 exercises the production default and only
    legacy mode opts out.
    
    ## Verification
    
    - `just test -p codex-core
    auto_compact_runs_after_resume_when_token_usage_is_over_limit
    auto_compact_counts_encrypted_reasoning_before_last_user
    auto_compact_runs_when_reasoning_header_clears_between_turns
    responses_lite_compact_request_uses_lite_transport_contract`
  • skills: make backend plugin skills invocable without an executor (#27387)
    ## Why
    
    #27198 made the extension-owned `codex_apps` MCP connection the hosted
    plugin runtime, but its `mcp/skill` resources still bypassed the skills
    extension. App-server could list and read those resources through
    generic MCP APIs, but a thread with no selected environment did not
    expose them in the model's skills catalog or load their `SKILL.md`
    through `$skill`.
    
    Hosted skills should stay remote while using the same typed catalog,
    source authority, deduplication, bounded contextual catalog, and
    selected-skill prompt injection as host and executor skills. They should
    not be downloaded or exposed as ambient filesystem paths.
    
    ## What changed
    
    - Add a session-scoped `McpResourceClient` over the replaceable MCP
    connection manager so resource list/read calls follow startup and
    refresh replacements.
    - Add a `BackendSkillProvider` that pages `codex_apps` resources,
    accepts bounded and validated `mcp/skill` entries, and reads a selected
    skill's `SKILL.md` through the same MCP connection.
    - Register the remote provider in app-server and include it in the
    skills catalog even when a thread has no selected capability roots or
    executor.
    - Contribute hosted skill metadata through the bounded
    `AvailableSkillsInstructions` developer-context path, exclude remote
    entries from per-turn catalog injection, and classify `<skills>`
    messages as contextual developer content so rollback can trim and
    rebuild them correctly.
    
    ## Testing
    
    - Extend the app-server MCP resource integration test with
    `environments: []` to exercise two-page discovery, filter a
    non-`mcp/skill` resource, verify the escaped developer catalog entry and
    user-role `<skill>` fragment containing the fetched `SKILL.md`, and
    preserve generic MCP resource reads.
    - Add core event-mapping coverage that classifies `<skills>` developer
    messages as contextual history.
  • [codex] Add comp_hash to model metadata (#27532)
    ## Summary
    - add optional `comp_hash` metadata to `ModelInfo`
    - update `ModelInfo` fixtures for the shared schema change
    - keep older model responses compatible by defaulting the field to
    `None`
    
    ## Why
    The models endpoint needs an opaque identifier for compaction-compatible
    model configurations. This PR only exposes that value in model metadata;
    it does not add it to turn context or change runtime behavior.
    
    Follow-up #27520 carries the value through turn context and rollouts,
    then uses it to trigger compaction.
    
    ## Stack
    - based directly on `main`
    - replaces #27519, which was accidentally merged into the wrong base
    branch
    - functionality follow-up: #27520
    
    ## Testing
    - `just test -p codex-protocol
    model_info_defaults_availability_nux_to_none_when_omitted`
    - `just fix -p codex-core -p codex-protocol -p codex-analytics -p
    codex-models-manager`
  • feat: add Bedrock API key as a managed auth mode (#27443)
    ## Why
    
    Codex needs to manage Amazon Bedrock API key credentials through the
    existing auth lifecycle instead of introducing a separate auth manager
    or provider-specific credential file. Treating Bedrock API key login as
    a primary auth mode gives it the same persistence, keyring, reload, and
    logout behavior as the existing OpenAI API key and ChatGPT modes.
    
    The credential is valid only for the `amazon-bedrock` model provider.
    OpenAI-compatible providers must reject this auth mode rather than
    treating the Bedrock key as an OpenAI bearer token.
    
    ## What changed
    
    - Added `bedrockApiKey` as an app-server `AuthMode` and
    `CodexAuth::BedrockApiKey` as a primary `AuthManager` mode.
    - Added `BedrockApiKeyAuth`, containing the API key and AWS region, to
    the existing `AuthDotJson` payload stored in `$CODEX_HOME/auth.json` or
    the configured keyring backend.
    - Added `login_with_bedrock_api_key(...)`, parallel to
    `login_with_api_key(...)`, which replaces the current stored login with
    Bedrock credentials.
    - Reused generic auth reload and logout behavior instead of adding a
    Bedrock-specific auth manager or logout path.
    - Updated login restrictions, status reporting, diagnostics, telemetry
    classification, generated app-server schemas, and auth fixtures for the
    new mode.
    - Added explicit errors when Bedrock API key auth is selected with an
    OpenAI-compatible model provider.
    
    This PR establishes managed storage and auth-mode behavior. Routing the
    managed key and region into Amazon Bedrock requests will be in follow-up
    PRs.
  • [codex] Remove redundant plugin app auth state (#27465)
    ## Summary
    
    - remove the redundant `needsAuth` field from `AppSummary` and generated
    app-server schemas
    - stop `plugin/read` from querying Apps MCP solely to hydrate unused
    connector auth state
    - preserve `plugin/install.appsNeedingAuth` membership and
    `app/list.isAccessible` as the authentication signals
    
    ## Why
    
    Codex App and TUI do not consume `plugin/read.plugin.apps[].needsAuth`.
    Hydrating it could establish an Apps MCP connection and discover tools
    on a cold `plugin/read` request, adding avoidable latency. The plugin
    APIs are still marked under development, so removing this wire field is
    preferable to retaining a misleading default.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `just fmt`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    plugin_install_uses_remote_apps_needing_auth_response`
    - `just test -p codex-app-server
    plugin_install_returns_apps_needing_auth`
    - `just test -p codex-app-server
    plugin_read_returns_plugin_details_with_bundle_contents`
    - `just test -p codex-tui
    plugin_detail_popup_snapshot_shows_install_actions_and_capability_summaries`
    - `$xin-build` simplify and debug reviews
  • Forward standalone assistant output to realtime (#27319)
    ## Why
    
    When a realtime session is open without an active frontend-model
    handoff, completed Codex assistant messages are currently dropped. That
    prevents the frontend model from hearing orchestrator preambles and
    final responses produced by typed turns or other non-handoff work, which
    makes the two models present as disconnected personas.
    
    Active handoffs already forward each completed assistant message,
    including preambles. This change leaves those V1 and V2 paths intact and
    fills only the no-active-handoff gap.
    
    ## What changed
    
    - Send standalone V1 assistant messages through
    `conversation.handoff.append` with a stable synthetic handoff ID
    - Send standalone V2 assistant messages as normal `[BACKEND]`
    `conversation.item.create` message items, then enqueue `response.create`
    so the frontend model responds
    - Preserve the existing active V1 and V2 transport and completion
    behavior
    - Continue excluding user messages from realtime mirroring
    - Skip empty output and cap each complete context injection, including
    its V2 prefix, at 1,000 tokens
    - Add end-to-end coverage for both wire formats, V2 response creation,
    preambles, final responses, and truncation
    
    ## Test plan
    
    - CI
  • Add app-server thread/delete API (#25018)
    ## Why
    
    Clients can archive and unarchive threads today, but there is no
    app-server API for permanently removing a thread. Deletion also needs to
    cover the full session tree: deleting a main thread should remove
    spawned subagent threads and the related local metadata instead of
    leaving orphaned rollout files, goals, or subagent state behind.
    
    ## What
    
    - Adds the v2 `thread/delete` request and `thread/deleted` notification,
    with the response shape kept consistent with `thread/archive`.
    - Implements local hard delete for active and archived rollout files.
    - Deletes the requested thread's state DB row as the commit point, then
    best-effort cleans associated state including spawned descendants,
    goals, spawn edges, logs, dynamic tools, and agent job assignments.
    - Updates app-server API docs and generated protocol schema/TypeScript
    fixtures.
  • Use plugin-service MCP as the hosted plugin runtime (#27198)
    ## Stack
    
    - Base: #27191
    - This PR is the third vertical and should be reviewed against
    `jif/external-plugins-2`, not `main`.
    
    ## Why
    
    #27191 moves the host-owned Apps MCP registration behind an extension
    contributor, but deliberately preserves the existing endpoint-selection
    feature while that contribution contract lands. App-server can therefore
    resolve the server through extensions, yet the hosted plugin endpoint is
    still selected through temporary `apps_mcp_path_override` plumbing.
    
    That is not the long-term plugin model. A plugin can bundle skills,
    connectors, MCP servers, and hooks, and those components do not all need
    the same source or execution environment. In particular, an
    authenticated HTTP MCP server can expose plugin capabilities directly
    from a backend without an executor or an orchestrator filesystem.
    
    This PR completes that hosted vertical. App-server's MCP extension now
    owns the aggregate hosted plugin runtime at `/ps/mcp`. Connector actions
    continue to arrive as MCP tools, while backend-provided skills arrive as
    MCP resources and use Codex's existing resource list/read paths. No
    second backend client, skill filesystem, or generic plugin activation
    framework is introduced.
    
    The backend route remains the hosted implementation. This change
    replaces Codex's temporary endpoint-selection mechanism, not the service
    behind the endpoint.
    
    ## What changed
    
    ### Hosted plugin runtime
    
    The MCP extension now contributes `codex_apps` as the hosted plugin
    runtime rather than as a configurable Apps endpoint:
    
    - `https://chatgpt.com` resolves to
    `https://chatgpt.com/backend-api/ps/mcp`;
    - a bare custom ChatGPT base resolves to `/api/codex/ps/mcp`;
    - the existing product-SKU header and ChatGPT authentication behavior
    are preserved;
    - executor availability is never consulted for this streamable HTTP
    transport.
    
    The same MCP connection carries both component shapes supported by the
    hosted endpoint:
    
    - connector actions are discovered and invoked as MCP tools;
    - hosted skills are enumerated and read as MCP resources through the
    existing `list_mcp_resources` and `read_mcp_resource` paths.
    
    This keeps component access in the subsystem that already owns the
    protocol instead of downloading backend skills into an orchestrator
    filesystem or inventing a parallel hosted-skill client.
    
    ### Explicit runtime ordering
    
    `McpManager` now resolves the reserved `codex_apps` entry in three
    ordered phases:
    
    1. install the legacy Apps fallback for compatibility;
    2. apply ordered extension `Set` or `Remove` overlays;
    3. apply the final ChatGPT-auth gate without synthesizing the server
    again.
    
    This ordering is important:
    
    - an ordinary configured or plugin MCP server cannot claim the
    auth-bearing `codex_apps` name;
    - an extension-contributed hosted runtime wins over the fallback;
    - an extension `Remove` remains authoritative;
    - a host without the MCP extension retains the legacy Apps endpoint and
    current local-only behavior.
    
    The temporary `legacy_apps_mcp_loader_enabled` coordination flag is no
    longer needed.
    
    ### Remove the path override
    
    The `apps_mcp_path_override` feature and its runtime plumbing are
    removed, including:
    
    - the feature registry entry and structured feature config;
    - `Config` and `McpConfig` fields;
    - config schema output;
    - config-lock materialization;
    - URL override handling in `codex-mcp`.
    
    Existing boolean and structured forms still deserialize as ignored
    compatibility input. They are omitted from new serialized config, and
    config-lock comparison normalizes the removed input so older locks
    remain replayable.
    
    ### App-server coverage
    
    App-server MCP fixtures now serve the hosted route at
    `/api/codex/ps/mcp`. Existing resource-read and tool/elicitation flows
    therefore exercise the extension-owned endpoint rather than succeeding
    through the legacy fallback.
    
    The stack also adds the missing `codex_chatgpt::connectors` re-export
    for the manager-backed connector helper introduced in #27191.
    
    ## Compatibility
    
    - App-server installs the extension and uses `/ps/mcp` for the hosted
    runtime.
    - CLI and other hosts that do not install the extension retain the
    legacy Apps endpoint.
    - Apps disabled or non-ChatGPT authentication removes `codex_apps` from
    the effective runtime view.
    - Existing local plugins, local skills, executor-selected skills,
    configured MCP servers, and MCP OAuth behavior are otherwise unchanged.
    - Backend plugin enablement remains account/workspace state owned by the
    hosted endpoint; this PR does not add thread-local backend plugin
    selection.
    
    ## Architectural fit
    
    The stack now proves two independent runtime shapes:
    
    1. #27184 resolves filesystem-backed skills through the executor that
    owns a selected root.
    2. #27191 and this PR resolve a backend-hosted HTTP MCP through an
    extension with no executor.
    
    Together they preserve the intended separation:
    
    - selection identifies a plugin/root when explicit selection is needed;
    - each component's owning extension resolves its concrete access
    mechanism;
    - execution stays with the runtime required by that component;
    - existing skills, MCP, connector, and hook subsystems remain the
    downstream consumers.
    
    ## Planned follow-ups
    
    1. **Executor stdio MCP:** selecting an executor plugin registers a
    manifest-declared stdio MCP server and executes it in the environment
    that owns the plugin.
    2. **Optional backend selection:** only if CCA needs thread-local
    selection distinct from backend account/workspace enablement, add a
    concrete backend-owned capability location and surface those selected
    skills through the skills catalog.
    3. **Connector metadata and hooks:** activate those plugin components
    through their existing owning subsystems, with executor hooks remaining
    environment-bound.
    4. **Propagation and persistence:** define explicit resume, fork,
    subagent, refresh, and environment-removal semantics once selected roots
    have multiple real consumers.
    5. **Local convergence:** migrate legacy local skill, MCP, connector,
    and hook paths behind their owning extensions one vertical at a time,
    then remove duplicate core managers and compatibility plumbing after
    parity.
    
    ## Verification
    
    Coverage in this change exercises:
    
    - extension-owned `/backend-api/ps/mcp` registration without an
    executor;
    - preservation of the legacy endpoint in hosts without the extension;
    - extension `Set` and `Remove` precedence over the legacy fallback;
    - ChatGPT-auth gating for the reserved server;
    - hosted MCP resource reads with and without an active thread;
    - connector tool invocation and MCP elicitation through the hosted
    route;
    - ignored boolean and structured forms of the removed path override;
    - config-lock replay compatibility for the removed feature.
    
    `cargo check -p codex-features -p codex-mcp-extension -p
    codex-app-server` passes. Tests and Clippy were not run locally under
    the current development instruction; CI provides the full validation
    pass.
  • [codex] Fix post-merge analytics integration failures (#27285)
    ## Why
    
    Recent merges left `main` with analytics integration build failures.
    Local Cargo runs also made the trimmed-skills test depend on
    developer-installed skills, while Bazel used an isolated home.
    
    ## What changed
    
    - Clone `thread_metadata.thread_source` when constructing goal analytics
    event parameters.
    - Group app-server thread extension inputs into
    `ThreadExtensionDependencies`.
    - Isolate the trimmed-skills test home so its exact fixture count is
    stable across Cargo and Bazel.
    
    ## Validation
    
    - `cargo check -p codex-analytics`
    - `just test -p codex-analytics` (71 tests)
    - `just test -p codex-app-server` (837 tests; one unrelated zsh-fork
    timeout passed on retry)
  • [codex-analytics] emit goal lifecycle analytics (#27078)
    ## Why
    - Currently, there is no analytics event for `/goal` behavior
    - Existing events cannot identify goal execution or its resulting
    outcome
    - The original update in
    [#26182](https://github.com/openai/codex/pull/26182) was implemented
    before `/goal` moved into `codex-goal-extension`.
    
    ## What Changed
    - Adds `codex_goal_event` serialization and enrichment to
    `codex-analytics`
    - Emits goal events from the canonical `codex-goal-extension` mutation
    and accounting paths:
      - `created` when a new logical goal is persisted
      - `usage_accounted` when cumulative goal usage is persisted
      - `status_changed` when the stored goal status changes
      - `cleared` when the goal is deleted
    - Preserves causal `turn_id` for turn driven events and uses null
    attribution for external or idle lifecycle events
    - Changes goal deletion to return the deleted row so `cleared` retains
    the stable goal ID
    
    ## Event Details
    
    Includes standard analytics metadata along with goal specific fields:
    - `goal_id`: Stable ID stored in the local SQLite goal row and shared
    across the goal's events
    - `event_kind`: Observed operation (see the 4 lifecycle events cited in
    the above bullet)
    - `goal_status`: Resulting or last stored status: `active`, `paused`,
    `blocked`, `usage_limited`, etc.
      - `has_token_budget`: Indicates whether a token budget is configured
      - `turn_id`: Causal turn ID, or null when no causal turn exists
    - `cumulative_tokens_accounted`: Cumulative tokens on `usage_accounted`
    events; null otherwise
    - `cumulative_time_accounted_seconds`: Cumulative active time on
    `usage_accounted` events; null otherwise
    
    ## Validation
    - `just test -p codex-analytics -p codex-state -p codex-goal-extension`
    - `just test -p codex-core -E 'test(/goal/)'`
    - `just test -p codex-app-server`
    - `cargo build -p codex-analytics -p codex-core -p codex-state -p
    codex-app-server`
  • Add per-session realtime model and version overrides (#24999)
    ## Why
    
    Clients need to select a realtime session configuration for an
    individual start without rewriting persisted configuration or restarting
    the app-server process.
    
    ## What Changed
    
    - Add optional `model` and `version` fields to `thread/realtime/start`
    - Forward those optional values through the realtime start operation and
    apply them only for that session
    - Preserve existing configured/default behavior when the new fields are
    omitted
    - Update generated protocol schema and app-server documentation
    
    ## Validation
    
    - Added/updated protocol serialization coverage for the new optional
    request fields
    - Added focused core coverage for a session override taking precedence
    over configured realtime selection
    - Added focused app-server coverage that a request override reaches the
    realtime WebSocket handshake
  • Load selected executor skills through extensions (#27184)
    ## Why
    
    CCA is moving toward a split runtime where the orchestrator may not have
    a filesystem, while executors can expose preinstalled plugins and
    skills. A thread therefore needs to select capabilities without asking
    app-server or core to interpret executor-owned paths through the
    orchestrator's filesystem.
    
    The longer-term model is broader than executor skills:
    
    - A plugin is a bundle of skills, MCP servers, connectors/apps, and
    hooks.
    - A plugin root can be local, executor-owned, or hosted by a backend.
    - Components inside one plugin can use different access and execution
    mechanisms. A skill may be read from a filesystem or through backend
    tools; an HTTP MCP server can run without an executor; a stdio MCP
    server or hook needs an execution environment.
    - Core should carry generic extension initialization data. The extension
    that owns a component should discover it, expose it to the model, and
    invoke it through the appropriate runtime.
    
    This PR establishes that architecture through one complete vertical:
    selecting a root on an executor, discovering the skills beneath it,
    exposing those skills to the model, and reading an explicitly invoked
    `SKILL.md` through the same executor.
    
    ## Contract
    
    `thread/start` gains an experimental `selectedCapabilityRoots` field:
    
    ```json
    {
      "selectedCapabilityRoots": [
        {
          "id": "deploy-plugin@1",
          "location": {
            "type": "environment",
            "environmentId": "workspace",
            "path": "/opt/codex/plugins/deploy"
          }
        }
      ]
    }
    ```
    
    The root is intentionally not classified as a "plugin" or "skill" in the
    API. It can point at a standalone skill, a directory containing several
    skills, or a plugin containing skills and other components. This PR only
    teaches the skills extension how to consume it; later extensions can
    resolve MCP, connector, and hook components from the same selection.
    
    The platform-supplied `id` is stable selection identity. The location
    says which runtime owns the root and gives that runtime an opaque path.
    App-server does not inspect or canonicalize the path.
    
    ## What changed
    
    ### Generic thread extension initialization
    
    App-server converts selected roots into `ExtensionDataInit`. Core
    carries that generic initialization value until the final thread ID is
    known, then creates thread-scoped `ExtensionData` before lifecycle
    contributors run.
    
    This keeps `Session` and core independent of the capability-selection
    contract. The initialization value is consumed during construction; it
    is not retained as another long-lived `Session` field.
    
    ### Executor-backed skills
    
    The skills extension now owns an `ExecutorSkillProvider` that:
    
    - resolves the selected environment through `EnvironmentManager`
    - discovers, canonicalizes, and reads skills through that environment's
    `ExecutorFileSystem`
    - contributes the bounded selected-skill catalog as stable developer
    context
    - reads an explicitly invoked skill body through the authority that
    listed it
    - warns when an environment or root is unavailable
    - never falls back to the orchestrator filesystem for an executor-owned
    root
    
    Skill catalog and instruction fragments have hard byte bounds, which
    also bound them below the 10K-token per-item context limit. If a
    selected executor skill has the same name as a legacy local skill, the
    executor selection owns that invocation and the local body is not
    injected a second time.
    
    Existing local and bundled skill loading remains in place. Omitting
    `selectedCapabilityRoots` therefore preserves current local-only
    behavior.
    
    ## Current semantics
    
    - Only environment-owned locations are represented in this first
    contract.
    - Roots are resolved by the destination extension, not by app-server or
    core.
    - An unavailable executor or invalid root produces a warning and no
    capabilities from that root; it does not trigger a local-filesystem
    fallback.
    - Selection applies to a newly started active thread.
    - MCP servers, connectors, and hooks beneath a selected plugin root are
    not activated yet.
    - Selection is not yet persisted or inherited across resume, fork, or
    subagent creation. Existing local capabilities continue to behave as
    they do today in those flows.
    
    ## Planned vertical follow-ups
    
    1. **Hosted HTTP MCP:** add an extension-backed HTTP MCP source that
    works without an executor, then replace the special-purpose MCP plugins
    loader with that implementation.
    2. **Executor MCP:** register and execute stdio MCP servers through the
    environment that owns the selected plugin root.
    3. **Backend skills:** add a hosted skill source whose catalog and
    bodies are accessed through extension tools rather than a filesystem.
    4. **Connectors and hooks:** activate those components through their
    owning extensions, using the same selected-root boundary and
    component-specific runtime.
    5. **Durable selection:** define the desired-selection lifecycle,
    persist it, and make resume, fork, and subagent inheritance explicit
    rather than accidental.
    6. **Local convergence:** incrementally route existing local plugin,
    skill, and MCP loading through the same extension model while preserving
    current local behavior.
    
    Each follow-up remains reviewable as an end-to-end capability. The
    platform selects roots, generic thread extension data carries the
    selection, and the owning extension resolves and operates its component.
    
    ## Verification
    
    Coverage added for:
    
    - app-server end-to-end discovery and explicit invocation of a skill
    inside an executor-selected plugin root
    - exclusive invocation when a selected executor skill collides with a
    local skill name
    - executor filesystem authority for discovery, canonicalization, and
    reads
    - thread extension initialization before lifecycle contributors run
    - stable executor catalog context, explicit invocation, context
    rebuilding, hidden skills, and preserved host/remote catalog behavior
    
    Targeted protocol, core-skills, skills-extension, core lifecycle, and
    app-server executor-skill tests were run during development.
  • app-server: reject direct input to multi-agent v2 sub-agents (#27173)
    ## Why
    
    Multi-agent v2 sub-agents are owned and coordinated by their parent
    agent. Allowing an app-server client to start or steer turns on a
    spawned child bypasses the multi-agent messaging path and creates a
    second, conflicting source of work for that sub-agent.
    
    ## What changed
    
    - Reject direct `turn/start` and `turn/steer` requests targeting
    multi-agent v2 thread-spawn sub-agents.
    - Identify these targets using both the thread's resolved multi-agent
    version and its `SubAgentSource::ThreadSpawn` session source, leaving
    root threads, v1 agents, and other sub-agent types unchanged.
    - Return a consistent invalid-request error before validating or
    applying the submitted input.
    
    ## Testing
    
    - Added an app-server integration test that spawns a real multi-agent v2
    child and verifies that direct `turn/start` and `turn/steer` requests
    are rejected.
  • Avoid rereading rollout history during cold resume (#27031)
    ## Summary
    
    - reuse the history-bearing `StoredThread` loaded while probing for a
    running thread
    - avoid rereading and reparsing the rollout when that probe finds no
    active process
    - reload after shutting down a loaded thread because shutdown may flush
    newer rollout items
    - add a regression test that verifies cold resume performs one
    history-bearing store read
    
    ## Problem
    
    `thread/resume` first reads the persisted thread with history while
    checking whether the thread is
    already running. When no running process exists, cold resume currently
    falls through to
    `resume_thread_from_rollout`, which reads and parses the same history
    again.
    
    That duplicate work grows with rollout size and remains on the
    synchronous resume path even when
    the caller requests `excludeTurns`.
    
    ## Background
    
    The duplicate read was introduced by #24528, which fixed resume
    overrides for idle cached
    threads. To support resumes specified by rollout path,
    `resume_running_thread` began loading the
    stored thread with history so it could resolve the canonical thread ID
    and determine whether a
    cached `CodexThread` was already loaded.
    
    That history is needed when the loaded-thread path handles the request.
    On a cold miss, however,
    the function's boolean result could only report that no loaded thread
    handled the request. It
    discarded the history-bearing `StoredThread`, and the normal cold-resume
    path immediately loaded
    and parsed the same rollout again.
    
    This change preserves the idle cached-thread behavior from #24528 while
    allowing the cold-resume
    path to reuse the probe result.
    
    ## Performance
    
    I benchmarked real retained rollouts using isolated `CODEX_HOME`
    directories, explicit rollout
    paths, debug builds of the commit and its exact parent, and alternating
    parent/patch order. The
    table below uses `thread/resume` with `excludeTurns: true`; response
    payload sizes were identical.
    
    | Rollout size | Records | Parent median | Patch median | Median paired
    saving |
    | ---: | ---: | ---: | ---: | ---: |
    | 6 MB | 3,574 | 541 ms | 441 ms | 132 ms |
    | 30 MB | 15,220 | 1.505 s | 1.041 s | 701 ms |
    | 60 MB | 31,453 | 2.644 s | 1.742 s | 970 ms |
    | 149 MB | 100,874 | 10.506 s | 7.156 s | 3.350 s |
    | 559 MB | 259,734 | 27.759 s | 16.725 s | 9.836 s |
    
    The absolute saving increases with thread size, as expected when
    removing one complete JSONL
    history read and parse. Total resume time is also content-dependent, so
    the relationship is not
    perfectly linear.
    
    I also tested full-history resume with `excludeTurns: false`. The
    response payload was
    byte-identical between variants, and the same size-dependent improvement
    remained visible:
    
    | Rollout size | Parent median | Patch median | Median paired saving |
    | ---: | ---: | ---: | ---: |
    | 6 MB | 1.052 s | 904 ms | 270 ms |
    | 30 MB | 2.667 s | 1.762 s | 924 ms |
    | 60 MB | 8.464 s | 6.272 s | 3.680 s |
    | 149 MB | 26.719 s | 12.118 s | 14.601 s |
    | 559 MB | 40.359 s | 25.475 s | 16.590 s |
    
    ## Validation
    
    - `just test -p codex-app-server
    cold_thread_resume_reuses_non_local_history_probe`
    - `just fix -p codex-app-server -p codex-thread-store`
    - `just fmt`
  • [codex] Return workspace directory installed plugins (#27098)
    ## Summary
    
    - return installed `workspace-directory` remote plugins by default in
    `plugin/installed`
    - keep shared-with-me installed plugins gated behind `plugin_sharing`
    - filter remote installed plugin marketplaces by canonical marketplace
    name instead of coarse workspace scope
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core-plugins`
    - `just test -p codex-app-server`
    - `just fix -p codex-core-plugins`
    - `just fix -p codex-app-server`
    - `$xin-build` targeted verification:
    - `just test -p codex-core-plugins
    build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name`
    - `just test -p codex-app-server
    plugin_installed_includes_workspace_directory_without_plugin_sharing`
    - `just test -p codex-app-server
    plugin_installed_includes_remote_shared_with_me_plugins`
    - `just test -p codex-app-server
    plugin_list_omits_shared_with_me_kind_when_plugin_sharing_disabled`