Commit Graph

7285 Commits

  • Remove async-trait from extension contributors (#27383)
    ## Why
    
    Extension contributors are registered behind `dyn Trait` objects, so
    native `async fn`/RPITIT methods would make these traits
    non-object-safe. Spell out the boxed, `Send` future contract directly so
    `extension-api` no longer needs `async-trait` while retaining the
    existing runtime model.
    
    ## What changed
    
    - add a shared `ExtensionFuture` alias and use it for asynchronous
    contributor methods
    - migrate production and test implementations to return `Box::pin(async
    move { ... })`
    - remove `async-trait` dependencies where they are no longer used,
    keeping it dev-only where unrelated test executors still require it
    
    ## Behavior
    
    No behavior change is intended. Contributor futures remain boxed,
    `Send`, dynamically dispatched, and lazily executed; cancellation and
    callback ordering stay unchanged.
    
    ## Testing
    
    - `just test -p codex-extension-api` (11 passed)
    - affected extension crates (64 passed)
    - targeted `codex-core` contributor tests (14 passed)
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    A broad local `codex-core` run compiled successfully but encountered
    unrelated sandbox and missing test-binary fixture failures; CI will run
    the full checks.
  • [codex] Tag multi-agent spawn metrics with version (#27375)
    ## Summary
    - tag legacy multi-agent spawn metrics with `version=v1`
    - tag multi-agent v2 spawn metrics with `version=v2`
    
    ## Why
    `codex.multi_agent.spawn` is emitted by both runtimes, so the existing
    metric cannot distinguish v2 adoption from aggregate multi-agent
    spawning. The bounded version tag makes that breakdown directly
    queryable without changing the counter's success-only semantics.
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    - Tests and Clippy were intentionally left to CI.
  • 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.
  • feat: keep child MCP warnings out of parent transcript (#27174)
    ## Why
    
    MCP startup status notifications are thread-owned, but `ChatWidget`
    trusted upstream routing. If routing state delivered a tagged child
    notification to the active parent widget, the child MCP failure could
    still mutate the parent's startup state and transcript. Rejecting it
    only inside the MCP handler was also too late because shared
    notification handling could already restore and consume the parent's
    retry status.
    
    ## What changed
    
    - Validate a tagged MCP status notification against the visible
    `ChatWidget` thread before shared notification handling mutates any
    parent state.
    - Cover child `Starting` and `Failed` notifications delivered to a
    retrying parent widget, asserting that they preserve its visible retry
    error and saved status header while producing no history or MCP status
    mutation.
    
    ## User impact
    
    Subagent MCP startup failures remain scoped to the child transcript
    instead of appearing as duplicate warnings in the parent transcript.
    
    ## Testing
    
    - `just test -p codex-tui mcp_startup_ignores_status_for_other_thread`
    - `just test -p codex-tui
    primary_thread_ignores_child_mcp_startup_notifications`
    - `just fmt`
  • [codex] Make MCP connection startup fallible (#27261)
    ## Why
    
    Required MCP server startup was enforced in `Session::new` after
    `McpConnectionManager` had already created the clients. That split let
    other manager construction paths bypass the same requirement and exposed
    manager internals solely so the session could validate them. Keeping
    required-server readiness in the constructor gives every caller one
    consistent startup contract.
    
    ## What changed
    
    - make `McpConnectionManager::new` return `anyhow::Result<Self>` and
    fail when an enabled, required server cannot initialize
    - pass the startup cancellation token into the constructor so
    required-server waits remain cancellable
    - propagate constructor failures through resource reads, connector
    discovery, and MCP status collection
    - preserve the active manager and cancellation token when a refreshed
    replacement fails
    - keep required-startup failure collection private and cover the
    constructor error contract directly
    
    ## Validation
    
    - updated the focused connection-manager test to assert the complete
    required-server startup error
    - local tests not run; relying on CI
  • Add spans to run_turn (#27107)
    ## Why
    Codex app-server latency traces do not granularly cover turn
    orchestration, sampling-request preparation, and tool-loading work.
    These spans help separate local coordination/setup costs from model
    streaming and tool execution.
    
    ## What changed
    - Add `run_turn.*` spans around sampling-request input preparation and
    post-sampling state collection
    - Add function-level trace spans around turn setup, hook execution,
    compaction, prompt construction, and MCP tool exposure
    - Add `built_tools.*` spans around plugin loading and discoverable-tool
    loading
    
    ## Verification
    Trigger Codex rollout and observe new spans are included
  • [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
  • Add spans to build_tool_router (#27094)
    ## Why
    - Local profiling shows `append_tool_search_executor` averages ~113ms
    per call. Adding a span lets us track this cost as we optimize in
    follow-up PRs, either by reducing the work or avoiding repeated rebuilds
    when inputs have not changed.
    - While we're here, we can add spans to `build_tool_router` and other
    sub-calls which code analysis shows may have additional opportunities
    for improvement.
    
    ## What changed
    Add function-level trace spans around `build_tool_router`,
    `build_tool_specs_and_registry`, `add_tool_sources`,
    `append_tool_search_executor`, and
    `build_model_visible_specs_and_registry`
    
    ## Verification
    Trigger Codex rollout and observe new spans are included
  • feat: use provider defaults for memory models (#27129)
    ## Why
    
    Memory startup used hardcoded OpenAI model slugs for extraction and
    consolidation. That works for the default OpenAI-compatible path, but
    provider-specific backends can require different model identifiers. In
    particular, Amazon Bedrock should use its Bedrock model ID for these
    background memory requests instead of the OpenAI `gpt-5.4-mini` /
    `gpt-5.4` slugs.
    
    ## What Changed
    
    - Added provider-owned preferred memory model methods alongside
    `approval_review_preferred_model`.
    - Updated memory extraction and consolidation to resolve their default
    model through the active `ModelProvider`.
    - Added Amazon Bedrock overrides so both memory stages use
    `openai.gpt-5.4` through Bedrock’s provider-specific model ID.
    - Kept explicit `memories.extract_model` and
    `memories.consolidation_model` config overrides taking precedence.
    - Added startup coverage for default OpenAI and Bedrock memory model
    selection.
    
    #closes #26288
  • [codex] Tighten MCP connection manager API visibility and order (#27257)
    ## Summary
    
    - order `McpConnectionManager` methods by visibility, with the primary
    constructor and public API first
    - restrict `list_available_server_infos` to `codex-mcp`
    - make `new_uninitialized` a private test-only helper
    
    ## Why
    
    The manager exposed methods that are only used inside `codex-mcp` or its
    unit tests. Tightening those methods keeps the exported API intentional,
    while the new ordering makes the supported surface easier to scan.
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - local tests not run; relying on CI
  • [codex] Retry streamable HTTP initialize failures (#25147)
    ## Summary
    - Retry transient streamable HTTP failures during RMCP startup when the
    failure happens while sending the initialize request.
    - Retry transient streamable HTTP failures for tools/list, which is
    read-only and safe to replay.
    - Cover both retryable HTTP statuses and request-layer failures where no
    HTTP status is returned.
    - Surface retryable HTTP statuses from the streamable HTTP adapter as
    typed client errors.
    - Add integration coverage for initialize retry, tools/list retry,
    no-status request failure retry, and non-retryable initialize status.
    
    ## Root cause
    The observed codex_apps failures can happen before normal tool
    execution: RMCP startup fails while sending initialize, or the first
    read-only tools/list fails after startup. Retrying hosted_apps_bridge
    tools/call would not cover initialize and would risk replaying
    side-effecting tool calls. This change retries the streamable HTTP
    handshake itself, recreates the transport between initialize attempts,
    and retries only tools/list among post-initialize service operations.
    
    ## Validation
    - cargo fmt --package codex-rmcp-client
    - cargo test -p codex-rmcp-client --test streamable_http_recovery
  • [2/4] Add private Python goal operations (#27111)
    ## Why
    
    The Python SDK must treat the runtime's initial goal turn and its
    continuations as one logical operation. That requires a private
    lifecycle engine before the public API can return the existing turn
    handle and result types.
    
    ## What
    
    - start goals by composing the existing clear/set goal RPCs
    - enforce persisted, idle threads and a bounded startup handshake
    - coalesce continuation notifications under a stable logical turn ID
    - aggregate items, usage, timing, and terminal status
    - support rollover-aware steering, interruption, cancellation, and
    cleanup
    - provide equivalent sync and async internals
    
    This is the second PR in the stack and intentionally adds no public API.
    
    ## Test plan
    
    - online CI, including the Python SDK suite
    - behavioral coverage is added in the following two stack PRs
  • Stop mirroring Codex user input into realtime (#27116)
    ## Why
    
    The realtime frontend model and the backing Codex thread should present
    one coherent assistant. Raw typed messages, steers, and worker reports
    belong to the orchestrator; the frontend model should receive the
    orchestrator's user-facing result rather than a second copy of those
    inputs.
    
    Today normal `turn/start` input is automatically inserted into the
    realtime conversation, while `turn/steer` is not. Besides creating
    inconsistent context, this can make the frontend model react
    independently before Codex has produced the response it should speak.
    
    ## What changed
    
    - Remove automatic accepted-user-input mirroring into realtime
    - Remove the mirror-only echo-suppression flag and dead V2 prefix helper
    - Preserve explicit app-to-realtime text injection and FEM-to-Codex
    delegation
    - Replace the positive mirror tests and obsolete snapshots with a
    negative routing regression test
    
    ## Test plan
    
    - `cargo test -p codex-core
    conversation_user_text_turn_is_not_sent_to_realtime`
    - `cargo test -p codex-core
    conversation_startup_context_is_truncated_and_sent_once_per_start`
    - `cargo test -p codex-core inbound_handoff_request_starts_turn`
  • [codex] Handle Ctrl-C for non-TTY unified exec (#26734)
    ## Why
    
    A long-running unified exec process started with `tty: false` could not
    be interrupted via `write_stdin`: ordinary non-TTY stdin writes are
    rejected once stdin is closed, but an exact U+0003 payload should still
    map to a process interrupt. The interrupt should flow through the same
    process lifecycle path as a real signal so Codex preserves
    process-reported output and exit metadata instead of fabricating a
    Ctrl-C exit code or tearing down the session early.
    
    ## What Changed
    
    - Add `process/signal` to exec-server with `ProcessSignal::Interrupt`
    and an empty response.
    - Add a non-consuming `ProcessHandle::signal` path for spawned
    processes; on Unix it sends SIGINT to the process group and leaves
    terminate/hard-kill unchanged.
    - Route non-TTY U+0003 `write_stdin` through `process.signal(...)`
    instead of `terminate`, then let the normal post-write collection path
    drain output and observe exit.
    - Add exec-server coverage where a shell `trap INT` handler prints the
    signal and exits with its own code.
    - Add unified exec coverage where a `tty: false` process traps SIGINT,
    emits output, and exits with its own code.
    
    ## Validation
    
    - `just test -p codex-exec-server
    exec_process_signal_interrupts_process`
    - `just test -p codex-exec-server`
    - `just test -p codex-core
    write_stdin_ctrl_c_interrupts_non_tty_session`
  • [codex] Report unusable MCP OAuth credentials as logged out (#26713)
    ## Why
    
    Persisted MCP OAuth credentials were reported as authenticated whenever
    a credential record existed. An expired token without a usable refresh
    token could therefore appear as `OAuth` even though startup could not
    authenticate with it, leaving users with a misleading status instead of
    a login prompt.
    
    ## What changed
    
    - Classify stored OAuth credentials as missing, usable, or requiring
    authorization.
    - Reuse the existing refresh window so near-expiry credentials without a
    refresh path are also treated as logged out.
    - Validate required credential fields before reporting OAuth
    authentication.
    - Add unit coverage for credential usability and integration coverage
    for expired, unexpired, and refreshable persisted credentials.
    
    ## Validation
    
    - `just test -p codex-rmcp-client`
  • [codex] Characterize global instruction lifecycle (#26830)
    ## Why
    
    Global instruction behavior spans thread creation, resume, forks,
    subagents, and compaction. Characterization coverage is needed before
    changing those semantics so preserved history can be distinguished from
    newly loaded configuration.
    
    ## What changed
    
    - Extends the existing `agents_md` suite with fresh-thread, warning,
    resume, fork, and subagent lifecycle coverage.
    - Extends the existing `compact` suite with manual, mid-turn, and
    remote-v2 compaction coverage.
    - Asserts rendered instruction fragments, reported source paths, and
    structured request history before and after instruction-file mutations.
  • Route hosted Apps MCP through extensions (#27191)
    ## Stack
    
    - Base: #27184
    - This PR is the second vertical and should be reviewed against
    `jif/external-plugins-1`, not `main`.
    
    ## Why
    
    CCA is moving toward a split runtime where the orchestrator may have no
    filesystem or executor, but it still needs to activate remotely hosted
    plugin components. HTTP MCP servers are the simplest complete example:
    they need configuration and host authentication, but they do not need an
    executor process.
    
    The Apps MCP endpoint is currently synthesized by a special-purpose
    loader inside the MCP runtime. That works locally, but it leaves hosted
    MCP activation outside the extension model being established in #27184.
    It also makes the Apps path a poor foundation for plugins whose skills,
    MCP servers, connectors, and hooks may come from different sources or
    execute in different places.
    
    This PR moves that one behavior behind an extension-owned contribution
    while preserving the existing local fallback. It deliberately does not
    introduce a generic plugin activation framework.
    
    ## What changed
    
    ### MCP extension contribution
    
    `codex-extension-api` gains an ordered `McpServerContributor` contract.
    A contributor returns typed `Set` or `Remove` overlays for MCP server
    configuration; later contributors win for the names they own.
    
    The contract stays at the existing MCP configuration boundary.
    Extensions do not create a second connection manager or transport
    abstraction.
    
    ### Hosted Apps MCP extension
    
    A new `codex-mcp-extension` contributes the reserved `codex_apps` server
    from the existing Apps feature, ChatGPT base URL, path override, and
    product SKU configuration.
    
    When `apps_mcp_path_override` is enabled for `https://chatgpt.com`, the
    resulting streamable HTTP endpoint is
    `https://chatgpt.com/backend-api/ps/mcp`. The existing ChatGPT-auth gate
    remains authoritative, so this server can run in an orchestrator-only
    process without being exposed for API-key sessions.
    
    ### One resolved runtime view
    
    `McpManager` now distinguishes three views:
    
    - **configured:** config- and plugin-backed servers before extension
    overlays;
    - **runtime:** configured servers plus host-installed extension
    contributions;
    - **effective:** runtime servers after auth gating and compatibility
    built-ins.
    
    App-server installs the hosted MCP extension and uses the runtime view
    for thread startup, refresh, status, threadless resource reads,
    connector discovery, and MCP OAuth lookup. This keeps
    `mcpServer/oauth/login` consistent with the servers exposed by the other
    MCP APIs. The hosted Apps server itself continues to use existing
    ChatGPT host authentication rather than MCP OAuth.
    
    ## Compatibility
    
    Hosts that do not install the MCP extension retain the existing Apps MCP
    synthesis path. This preserves current local-only, CLI, and
    standalone-host behavior while app-server exercises the extension path.
    
    Disabling Apps removes the reserved `codex_apps` entry, and losing
    ChatGPT auth removes it from the effective runtime view. Executor
    availability is not consulted for this HTTP transport.
    
    ## Follow-ups
    
    The next vertical will resolve a manifest-declared stdio MCP server from
    an executor-selected plugin root and execute it in the environment that
    owns that root. Later verticals can add backend-owned skills, connector
    metadata, hooks, durable selection semantics, and incremental local
    convergence without changing the component-specific runtime boundaries
    introduced here.
    
    ## Verification
    
    Focused coverage was added for:
    
    - contributing the hosted Apps MCP at `/backend-api/ps/mcp` without an
    executor;
    - requiring ChatGPT auth in the effective runtime view;
    - removing a reserved configured Apps server when the Apps feature is
    disabled.
    
    `cargo check -p codex-app-server -p codex-mcp-extension -p
    codex-extension-api -p codex-mcp` passed. Tests and Clippy were not run
    locally under the current development instruction; CI provides the full
    validation pass.
  • [1/4] Add Python goal routing foundation (#27110)
    ## Why
    
    Goal continuation turns are emitted by the existing runtime as separate
    physical turns. The Python SDK needs private thread-scoped routing
    before it can present those notifications as one logical operation,
    without changing ordinary turn routing or the app-server protocol.
    
    ## What
    
    - add private goal operation state and thread-scoped notification
    routing
    - add internal wrappers for the existing `thread/goal/clear` and
    `thread/goal/set` RPCs
    - include existing goal notifications in the SDK notification union
    - preserve ordinary turn-ID routing unchanged
    - add focused routing coverage
    
    This PR does not expose a public goal API. It is the first PR in the
    Python goal operations stack.
    
    ## Test plan
    
    - online CI, including the Python SDK suite
    - focused typed-notification routing coverage
  • Reduce TUI legacy core dependencies (#26711)
    ## Why
    
    The TUI still reached through `app-server-client::legacy_core` for
    thread-name normalization and project-instruction filename details. In
    particular, checking the TUI's local filesystem for `/init` is incorrect
    for remote app-server sessions, where the server owns the working
    directory and instruction discovery.
    
    ## What changed
    
    - use the instruction source paths supplied by the app server to decide
    whether `/init` should avoid overwriting project instructions
    - keep the small thread-name normalization helper local to the TUI
    - remove the now-unused instruction filename constants, utility module,
    and other unused `legacy_core` re-exports
    - make status helper tests independent of concrete instruction filenames
    
    ## Verification
    
    - `just test -p codex-app-server-client`
    - `just test -p codex-tui
    slash_init_skips_when_project_instructions_are_loaded`
    - `just test -p codex-tui` ran 2,799 tests; 2,797 passed and two
    unrelated guardian feature-flag tests failed reproducibly in untouched
    code
    
    ### Manual test
    
    Started an app server over WebSocket with a remote workspace containing
    `AGENTS.md`, then connected the TUI using `--remote`. After confirming
    `thread/start` returned the file in `instructionSources`, deleted
    `AGENTS.md` and ran `/init` in the existing session.
    
    The TUI still reported that project instructions already existed and
    skipped `/init`. The trace contained no `turn/start` request, confirming
    the decision came from app-server session state rather than a new
    client-local filesystem check.
  • Allow creating a new goal after completion (#26681)
    ## Why
    
    Users have indicated that they want an agent to be able to create a new
    goal for itself after completing the previous goal. Currently, that's
    not possible because agents cannot overwrite an existing goal even if
    it's complete. This PR removes this limitation and allows `create_goal`
    to overwrite an existing goal if it is in the `complete` state.
    
    ## What changed
    
    `create_goal` now replaces the existing goal only when its status is
    `complete`. The replacement is performed atomically in the goal store,
    creates a fresh active goal with reset usage, and continues to reject
    creation while any unfinished goal exists. App server clients see a
    single `thread/goal/updated` event when the previous goal is replaced
    with the new one.
    
    The tool description and error message now reflect these semantics.
    
    ## What didn't change
    
    Agents are not allowed to create a new goal (overwrite their existing
    goal) if an existing goal is still active, blocked, paused, or in any
    other state other than "completed".
  • Add SOCKS5 TCP MITM coverage (#22685)
    ## Summary
    - reuse the MITM HTTPS serving path for raw SOCKS5 TCP streams
    - route limited-mode and hooked SOCKS5 TCP requests through MITM before
    dialing upstream
    - keep SOCKS5 UDP limited-mode behavior unchanged
    
    ## Validation
    - `just fmt`
    - `just test -p codex-network-proxy`
    - `just fix -p codex-network-proxy`
    - `git diff --check`
  • [codex] Speed up local nextest runs (#26479)
    ## Why
    
    `just test` currently uses the CI-oriented nextest profile, which
    serializes app-server integration tests even on developer machines that
    can run several safely. Bounded local parallelism substantially shortens
    this common iteration loop without changing CI behavior.
    
    Eight-worker experiments were faster, but keeping them reliable required
    relaxing several test deadlines. Four workers for integration tests is a
    solid tradeoff that speeds up local testing without needing to change
    test logic.
    
    ## What changed
    
    - Add a `local` nextest profile that inherits the existing defaults.
    - Allow up to four app-server integration tests to run concurrently
    under that profile.
    - Make `just test` select the local profile on Unix and Windows.
    - Keep the default CI profile serialized and leave all test deadlines
    unchanged.
    
    The tests use separate processes, randomized temporary `CODEX_HOME`
    directories, and ephemeral ports. The remaining shared constraints are
    system resources; each app-server also uses a multi-thread Tokio
    runtime, and fuzzy-search tests can create additional worker threads, so
    the local cap remains intentionally conservative.
    
    ## Performance and validation
    
    All measurements below are warm, execution-only app-server runs with
    nextest retries disabled.
    
    On the current rebased branch, an AMD EPYC 7763 machine with 16 logical
    CPUs and 62 GiB RAM completed three consecutive runs:
    
    | Run | Nextest time | Wall time | Result |
    | --- | ---: | ---: | --- |
    | 1 | 142.941s | 145.17s | 836/836 passed |
    | 2 | 143.402s | 145.59s | 836/836 passed |
    | 3 | 142.870s | 145.08s | 836/836 passed |
    
    The mean wall time was 145.28s. The slow-inventory, approval replay, and
    zsh-fork tests all passed with their original deadlines.
    
    Earlier measurements on the same Linux machine, before the suite grew,
    showed the scaling that motivated the change:
    
    | App-server concurrency | Nextest time | Result |
    | --- | ---: | --- |
    | 1 | 369.5s | 572/572 passed |
    | 2 | 194.5s | 572/572 passed |
    | 4 | 111.0s mean over 3 runs | 3/3 clean |
    
    Four workers reduced that execution time by about 70%, a roughly 3.3x
    speedup over serialization.
  • [codex-analytics] add extensible feature thread sources (#27063)
    ## Why
    - `ThreadSource` currently defines a closed set of core-owned values
    - Product features also create threads for background or scheduled work
    - Adding every product-specific value to the core enum would require
    repeated `codex-rs` protocol changes
    - Feature-backed values let product callers provide precise attribution
    while preserving the existing core classifications
    
    ## What Changed
    - Adds `ThreadSource::Feature(String)` for app-owned thread source
    values
    - Represents all app-server v2 thread sources as scalar strings, so a
    feature source is supplied as `"automation"`
    - Persists and emits the feature's plain string label, so `"automation"`
    produces `thread_source="automation"` in analytics
    - Keeps `user`, `subagent`, and `memory_consolidation` as explicit
    core-owned values and regenerates the app-server schemas and TypeScript
    bindings
    
    ## Verification
    - `just write-app-server-schema`
    - `cargo check --workspace`
    - `just test -p codex-protocol
    feature_thread_source_serializes_as_its_app_owned_label`
    - `just test -p codex-app-server-protocol
    thread_sources_round_trip_as_scalar_labels`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `just fmt`
  • [codex] Test extension API contracts (#26835)
    ## Why
    
    `codex-extension-api` defines contracts shared by extension crates and
    their hosts, but it had no direct test suite. Host and feature tests
    cover downstream behavior, while regressions in the API crate's own
    typed state, registry ordering, and capability adapters could go
    unnoticed.
    
    ## What
    
    - Add public-surface integration tests for `ExtensionData`, including
    concurrent initialization and poison recovery.
    - Cover contributor registration order, approval short-circuiting, event
    sink retention, no-op response injection, and closure-based agent
    spawning.
    - Add the test-only dependencies used by the suite.
    
    ## Validation
    
    - `just test -p codex-extension-api`
    - `just argument-comment-lint -p codex-extension-api`
    - `just bazel-lock-check`
  • 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.
  • fix: Prevent /review crash when entering Esc on steer message (#22879)
    This changes the `/review` escape path so `Esc` no longer behaves like
    the normal queued-follow-up interrupt flow while a review is running.
    Steering is not currently supported in `/review` mode, without this
    change users are able to attempt a steer but it leads to a crash (see
    #22815). If the user has already tried to send additional guidance
    during `/review`, the TUI now keeps the review running and shows a
    warning that steer messages are not supported in that mode, while still
    pointing users to `Ctrl+C` if they actually want to cancel. It also adds
    regression coverage for the review-specific warning behavior. When users
    do cancel with Ctrl+C during /review, the TUI now tolerates the
    active-turn race that can happen during review handoff, and any queued
    steer messages are restored to the composer instead of being discarded.
    
    - Special-case `Esc` during an active `/review` when follow-up steer
    input is pending or has already been deferred.
    - Show a clear warning instead of interrupting the running review.
    - Make the Ctrl+C cancel path during /review resilient to active-turn
    races, while preserving any queued steer text by restoring it to the
    composer.
    - Add review-mode test coverage for the warning path.
    
    ## Testing
    
    1. Start a `/review` with a diff large enough that the review stays
    active for more than a few seconds.
    
    2. While the review is still running, type a follow-up / steer message,
    submit it, and then press `Esc`.
       Before: `Esc` causes the TUI to close abruptly.  
    After: the review keeps running and the transcript shows a warning that
    steer messages are not supported during `/review`, with guidance to use
    `Ctrl+C` if you want to cancel.
    
    3. Press `Ctrl+C` if you actually want to stop the review.  
    Before: (after restarting the test since Pt. 2 crashed) this is the
    intentional cancellation path.
    After: this remains the intentional cancellation path, and any queued
    follow-up steer text is restored to the composer instead of being lost.
       
    ## Note:
    `/review` mode explicitly does not support steering at this time (as
    noted in `turn_processer.rs`, if we want to explore that in the future
    this code will need to be modified). This change keeps unsupported steer
    attempts from crashing the TUI and preserves queued follow-up text if
    the user cancels with Ctrl+C.
  • 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`
  • Avoid no-op backfill state writes (#26420)
    ## Summary
    
    - avoid acquiring SQLite's writer slot when the singleton backfill row
    already exists
    - preserve race-safe repair when the row is missing
    - add regressions for writer contention and missing-row repair
    
    ## Why
    
    State runtime initialization and backfill-state reads previously
    executed
    `INSERT ... ON CONFLICT DO NOTHING` even in the steady state. SQLite
    still
    enters the writer path for that statement, so TUI and app-server startup
    could
    wait behind another writer for up to the configured five-second busy
    timeout.
    
    ## Validation
    
    - `just test -p codex-state` (134 tests passed)
    - `just fix -p codex-state`
    - `just fmt`
  • [codex] Ignore pending PR review comments (#27080)
    ## Why
    
    The PR babysitter could surface inline comments from a GitHub review
    that was still in the `PENDING` state. That allowed Codex to start
    acting on feedback before the reviewer submitted it.
    
    ## What changed
    
    - Correlate inline comments with their parent review and ignore pending
    reviews and their comments.
    - Remove pending review IDs from saved watcher state so the feedback
    surfaces normally after publication.
    - Update the skill instructions and add regression coverage for the
    draft-to-published transition.
    
    ## Validation
    
    - `python3 -m pytest
    .codex/skills/babysit-pr/scripts/test_gh_pr_watch.py`
    - Skill package validation with `quick_validate.py`
    - Live verification on #26835: the draft comment stayed hidden and
    surfaced after the review was submitted.
  • app-server: clear stale thread watches after v2 agent interruption (#27166)
    ## Why
    
    PR #27007 moved MultiAgentV2 interruption reporting from the legacy
    collaboration close event to `SubAgentActivity::Interrupted`.
    App-server's missing-thread cleanup still ran only for the legacy event,
    so an interrupted child that had already been unloaded could remain
    marked as loaded and running in `ThreadWatchManager`. That leaves thread
    status and running-turn accounting stale, including the count used
    during graceful shutdown.
    
    ## What changed
    
    - Handle `SubAgentActivity::Interrupted` separately in app-server event
    processing.
    - Remove the child's thread watch when `ThreadManager` no longer has
    that thread.
    - Continue forwarding the same completed sub-agent activity notification
    to clients.
    
    ## Testing
    
    - Added a regression test that starts with a running watch for an
    unloaded child, applies the interrupted activity event, and verifies the
    watch is removed, the running count returns to zero, and the client
    notification is still emitted.
  • multi-agent: add path-based v2 activity tracking (#27007)
    ## Why
    
    Multi-agent v2 identifies agents by canonical paths, but its tool
    handlers still emitted the larger legacy collaboration begin/end events
    built around nickname and role metadata. App-server, rollout-trace,
    analytics, and TUI consumers therefore lacked one compact path-based
    completion signal that behaved consistently across live events and
    replay.
    
    The TUI also needs a bounded `/agent` status surface for v2 agents. It
    should use recent local activity for previews, refresh liveness without
    loading full histories, and keep the legacy picker available when no
    path-backed v2 agent is known.
    
    ## What changed
    
    - Replace the v2 `spawn_agent`, `send_message`, `followup_task`, and
    `interrupt_agent` legacy lifecycle emissions with a success-only
    `SubAgentActivity` event. The event records the tool call ID, occurrence
    time, affected thread, canonical agent path, and `started`,
    `interacted`, or `interrupted` kind.
    - Expose the activity as a completion-only app-server v2
    `subAgentActivity` thread item in live notifications and reconstructed
    history, regenerate the protocol schemas, and count it in sub-agent tool
    analytics.
    - Track canonical paths from live activity and loaded-thread metadata in
    the TUI, and render the activity in live and replayed transcripts.
    - Make `/agent` list running path-backed agents with summaries from
    bounded local event buffers. Each summary is capped at 240 graphemes,
    the scan is capped at six recent items, only the last three wrapped
    lines are shown, and command output is omitted. Liveness falls back to
    metadata-only `thread/read` when local turn state is unavailable.
    - Persist the activity as a terminal rollout-trace runtime payload and
    reduce it to the corresponding spawn, send, follow-up, or close
    interaction edge. `interrupt_agent` is classified as a close-edge
    operation.
    - Preserve the legacy picker when no path-backed v2 agent is known.
    
    ## Compatibility
    
    App-server v2 clients that consumed `collabAgentToolCall` begin/end
    pairs for these tools must handle the new completion-only
    `subAgentActivity` item. Legacy v1 collaboration behavior is unchanged.
    
    ## Screenshot
    
    <img width="684" height="288" alt="Screenshot 2026-06-08 at 15 40 47"
    src="https://github.com/user-attachments/assets/194b3cd0-619d-45fb-b587-cf3e2b1b8a1d"
    />
    
    ## Testing
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-rollout-trace`
    - Added focused coverage for activity analytics, terminal trace
    serialization, spawn-edge reduction, `interrupt_agent` classification,
    TUI status rendering without aggregated command output, and clearing
    stale running state after a completed turn.
  • [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`
  • Use server app auth requirements for remote plugin install (#27085)
    ## Summary
    - request `includeAppsNeedingAuth=true` when installing remote plugins
    - return backend-provided `app_ids_needing_auth` from the remote install
    client
    - use those app IDs to populate `appsNeedingAuth` without refetching
    accessible apps, with fallback for older responses
    
    ## Testing
    - `just fmt`
    - `just test -p codex-app-server`
    - `just test -p codex-core-plugins`
    - real app-server install/uninstall check with Notion remote plugin
    - subagent review found no blocking issues
  • [codex] preserve fsmonitor for worktree Git reads (#26880)
    Codex forces `core.fsmonitor=false` on internal Git commands so a
    repository cannot select an executable fsmonitor helper. This also
    disables Git's built-in daemon for `status`, `diff`, and `ls-files`,
    turning those worktree reads into full scans in large repositories.
    
    Read the raw effective `core.fsmonitor` value and preserve it only when
    Git interprets it as true and advertises built-in daemon support through
    `git version --build-options`. Query uncommon boolean spellings back
    through Git using the exact effective value. Unset, false, helper paths,
    malformed values, probe failures, and unsupported Git builds continue to
    force `core.fsmonitor=false`.
    
    Centralize this policy in `git-utils` while keeping process execution in
    the existing local and workspace-command adapters. Probe once per
    worktree workflow and reuse the result for its Git commands, including
    the TUI `/diff` path. Metadata-only commands and repository discovery
    remain disabled without probing. Each probe and requested Git process
    keeps its own existing timeout, and the decision is not cached because
    layered and conditional Git configuration can change while Codex runs.
    
    ---------
    
    Co-authored-by: Chris Bookholt <bookholt@openai.com>
  • [codex] Remove remote compaction failure log (#27106)
    ## Why
    
    `log_remote_compact_failure` was the only consumer of the
    compact-request logging payload and most of the token-usage breakdown
    fields. Once that failure log is removed, keeping the surrounding
    carrier types leaves dead plumbing in the compaction path and context
    manager.
    
    ## What changed
    
    - Remove `log_remote_compact_failure`, `CompactRequestLogData`, and the
    v2 wrapper that only fed that log.
    - Let both remote compaction implementations return the original
    compaction error directly.
    - Replace `TotalTokenUsageBreakdown` with a narrow helper that returns
    only the remaining value needed by compaction analytics.
    - Keep `estimate_response_item_model_visible_bytes` private to the
    context manager implementation.
    
    ## Validation
    
    - `cargo check -p codex-core`
  • Preserve cloud requirements across TUI thread resets (#25177)
    Fixes a TUI regression where thread transitions such as `/new` and
    `/clear` could rebuild config without the cloud requirements loader,
    allowing users to fall back to non-cloud-managed settings. The config
    refresh path now preserves cloud requirements during thread
    reinitialization, and config loading is moved off the deep TUI event
    stack to avoid stack-overflow crashes during those reloads.
    
    - Passes the cloud requirements loader through TUI config rebuild paths.
    - Keeps cloud requirements applied for `/new`, `/clear`, `/fork`, side
    conversations, and session picker transitions.
    - Runs config building on a Tokio task so reloads do not occur on the
    deep TUI caller stack.
    - Adds regression coverage that cloud requirements survive
    thread-transition config refreshes.
    
    ## Test/Repro:
      - Start Codex with a cloud requirement applied.
      - Use `/new` or `/clear`.
    - The refreshed/fresh-session config should still include the cloud
    requirements
      
    This can be tested with any config item, at this moment for oai staff
    the easiest item to test is the `mentions_v2` feature. This is currently
    enabled in cloud requirements, but is not enabled by default. As a
    result, prior to these changes that feature is disabled after `/new` or
    `/clear`. Testing the same steps with a binary from this branch should
    not drop the feature enablement.
  • Update web search citation prompt (#27096)
    ## Summary
    
    - Update the web search tool prompt to require Markdown links for cited
    sources.
    - Explicitly tell the model not to use `turnX`-style citations in
    responses.
    
    ## Context
    
    
    https://openai.slack.com/archives/C0AU83S0ZQU/p1780964147777649?thread_ts=1780352049.512299&cid=C0AU83S0ZQU
    
    ## Test plan
    
    - `git diff --check`
    - `python3 scripts/format.py --check` (fails only on Rust formatter
    setup: rustup cannot create temp files under `/home/dev-user/.rustup`;
    Just and Python formatter checks pass when using temp cache dirs)
  • Show effective sandbox modes in /debug-config (#27068)
    ## Summary
    - Render `/debug-config`'s `allowed_sandbox_modes` from the finalized
    permission constraints instead of the raw requirements list.
    - Add regression coverage for configured full-access and external
    sandbox modes being omitted when effective permissions reject them.
    
    ## Details
    `allowed_sandbox_modes` comes from managed requirements, but the final
    permissions can be further constrained by derived validation rules. For
    example, `permissions.filesystem.deny_read` requires sandbox
    enforcement, so modes that disable or externalize Codex's sandbox are
    not actually usable even if they were present in the raw requirements
    TOML.
    
    The debug renderer now enumerates the configured sandbox-mode labels and
    keeps only those accepted by `Config.permissions`. That makes
    `/debug-config` reflect the same effective permission-profile constraint
    path used by runtime config validation, while preserving the existing
    source/provenance display.
    
    ## Validation
    - Added a regression test for effective sandbox-mode filtering in
    `/debug-config`.
  • fix(tui): linkify complete bare URLs with tildes (#27088)
    ## Background
    
    Bare URLs containing `~` in their path are currently only clickable up
    to the tilde in the interactive TUI. For example, Codex renders the
    visible text for:
    
    
    `https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
    
    but the OSC 8 destination stops at `https://www.cs.tufts.edu/`. This
    makes Cmd-click open the wrong location even though the terminal
    recognizes the complete URL outside Codex.
    
    Fixes #26774.
    
    ## Root Cause
    
    The URL scanner already accepts `~`. The truncation happens earlier:
    with strikethrough parsing enabled, `pulldown-cmark` splits this URL
    into adjacent decoded `Event::Text` values around the tilde. The
    Markdown renderer annotated each text event independently, so only the
    first event still looked like a complete URL with a supported scheme.
    
    The renderer now merges adjacent decoded text events before URL
    annotation. It preserves the combined source range while retaining
    parser-decoded contents, which avoids regressing entities such as
    `&amp;`.
    
    ## Changes
    
    - Add a small iterator that merges adjacent decoded Markdown text events
    and their source ranges.
    - Apply it at the Markdown renderer boundary before hyperlink detection.
    - Add regression coverage for the reported URL in prose, wrapped table
    output, and entity-decoded URLs.
    
    ## How to Test
    
    1. Run Codex with `just c`.
    2. Ask the assistant to output this exact bare URL with no Markdown link
    syntax:
    
    `https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
    3. Hold Cmd and hover or click the URL.
    4. Confirm the complete URL, including the suffix after `~`, is one
    destination.
    5. Repeat with the URL inside a Markdown table and confirm wrapped
    portions retain the same complete destination.
    
    Targeted tests:
    
    - `just test -p codex-tui url_with_tilde`
    - `just test -p codex-tui merged_text_events_preserve_entity_decoding`
    
    The full `codex-tui` test run was also executed. Its only failures were
    the two existing Guardian feature-flag tests:
    
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
  • Add typed file URIs (#26840)
    ## Why
    
    Codex needs stable `file:` URI identifiers that can cross process and
    operating-system boundaries without eagerly interpreting them as native
    paths. Existing fields also need to keep accepting absolute path strings
    during migration.
    
    ## What changed
    
    - Add `codex-utils-path-uri` with a validated, immutable `PathUri`
    wrapper that currently accepts only `file:` URLs.
    - Expose URI-level `basename`, `parent`, and `join` operations that
    preserve authorities and percent encoding without guessing the source
    operating system.
    - Keep native conversion explicit through `AbsolutePathBuf` and the
    current host rules.
    - Serialize as canonical URI text while accepting both URI text and
    legacy absolute native paths during deserialization.
    - Add adversarial coverage for Windows-looking and POSIX paths, UNC
    authorities, encoded metadata characters, non-UTF-8 POSIX paths, URI
    hierarchy operations, and legacy serde round trips.
  • chore: preserve one more schema layer during large tool compaction (#27084)
    ## Summary
    
    Some customer MCP tools expose large input schemas that exceed Codex's
    compact schema budget even after description stripping. Today, the final
    compaction pass collapses complex schemas starting at depth 2, which can
    erase important shallow call structure such as small `anyOf` branches,
    required fields, and help-mode entry points. In one reported case, this
    degraded a tool schema into `query: any | any`, leaving the model
    without enough structure to discover the required help call.
    
    This change raises the deep-schema collapse boundary from depth 2 to
    depth 3. That preserves one additional layer of the tool contract while
    still collapsing deeper expensive subtrees to `{}` when a schema remains
    over budget.
    
    ## What Changed
    
    - Increased `MAX_COMPACT_TOOL_SCHEMA_DEPTH` from `2` to `3`.
    - Updated the schema compaction traversal test to assert the new
    collapse boundary.
    - The resulting compacted shape keeps useful shallow structure, for
    example:
      - top-level argument names
      - shallow `anyOf` branches
      - required object fields
      - nested property names one level deeper than before
    
    ## Validation
    
    - Ran `just test -p codex-tools`: 81 tests passed.
    - Ran a golden schema corpus comparison over 214 discovered tool input
    schemas under `golden_schemas/*/mcp_tools/*/input_schema.json`.
    - Depth 2 and depth 3 had identical percentile token counts across the
    corpus.
      - Both ended with `0 / 214` schemas over 1k tokens.
    - Both ended with `0 / 214` schemas over the 4,000-byte compact JSON
    budget.
    - Only one golden schema changed, increasing from 49 to 56 tokens, so
    this does not appear to introduce a meaningful corpus-wide regression.
    
    Corpus percentile results:
    
    | Percentile | Depth 2 | Depth 3 |
    |---|---:|---:|
    | p0 | 9 | 9 |
    | p10 | 31 | 31 |
    | p25 | 54 | 54 |
    | p50 | 81 | 81 |
    | p75 | 143 | 143 |
    | p90 | 290 | 290 |
    | p95 | 431 | 431 |
    | p99 | 600 | 600 |
    | max | 832 | 832 |
  • feat(doctor): report editor and pager environment (#27081)
    ## Background
    
    This was prompted by
    [#26858](https://github.com/openai/codex/issues/26858), where the
    attached doctor report did not include the editor selection and I had to
    [ask which editor was in
    use](https://github.com/openai/codex/issues/26858#issuecomment-4653829891)
    before investigating the external-editor newline issue. Capturing these
    variables in doctor makes that context available up front in future
    reports.
    
    `codex doctor` is intended to capture enough local context to diagnose
    startup and terminal behavior, but it did not report the environment
    variables that select an external editor or configure command pagers.
    
    The TUI [prefers `VISUAL` over
    `EDITOR`](https://github.com/openai/codex/blob/56554904babcaacf4444a2cc90716880837dff7c/codex-rs/tui/src/external_editor.rs#L31-L38),
    so missing or unexpected values can explain why the external-editor
    shortcut fails or launches the wrong command. Pager values are also
    useful inherited-shell context even though [unified exec normalizes its
    effective pager variables to
    `cat`](https://github.com/openai/codex/blob/56554904babcaacf4444a2cc90716880837dff7c/codex-rs/core/src/unified_exec/process_manager.rs#L60-L70).
    
    These variables can contain arbitrary command arguments or inline
    environment assignments. The human report is local, but `codex doctor
    --json` may be attached to feedback, so the machine-readable report
    should not include their raw contents.
    
    ## What Changed
    
    - Report `VISUAL` and `EDITOR` in the system environment details, using
    `not set` when either variable is absent.
    - Report inherited `PAGER`, `GIT_PAGER`, `GH_PAGER`, and `LESS` values
    when present.
    - Preserve full values in local human output while reducing these fields
    to `set` or `not set` in redacted JSON output.
    - Add structured check, JSON-redaction, rendered-output, and snapshot
    coverage.
    
    ## How to Test
    
    1. From `codex-rs`, run Codex with explicit editor and pager variables:
    
       ```sh
    env VISUAL='code --wait' EDITOR=vim PAGER='less -R' GIT_PAGER=delta
    GH_PAGER=less LESS=-FRX \
         cargo run -p codex-cli --bin codex -- doctor --no-color
       ```
    
    2. Confirm the `system` details show the full values for all six
    variables.
    3. Unset the pager variables and rerun the command. Confirm pager rows
    are omitted while missing editor variables are shown as `not set`.
    4. Run the same configured environment with `doctor --json`. Confirm
    each configured editor or pager field is reported as `set` and none of
    the raw commands or arguments appear in the JSON.
    
    Targeted tests:
    
    - `just test -p codex-cli` (279 tests passed)
  • [codex] Add OTEL counter descriptions (#26091)
    ## Why
    
    Metric descriptions should be declared with reusable OTEL instruments
    instead of being coupled to individual consumers. Counter descriptions
    are the smallest API primitive needed by the exec-server observability
    work.
    
    ## What changed
    
    - Adds `counter_with_description` while preserving the existing counter
    API.
    - Caches counters by name and description so instrument metadata remains
    part of the declaration identity.
    - Covers the exported description together with the existing value and
    attribute contract.
    
    This PR only adds counter descriptions. It does not add gauges,
    second-based durations, or exec-server adoption.
    
    ## Stack
    
    1. **#26091: counter descriptions**
    2. #27057: gauge instruments
    3. #27058: second-based duration histograms
    
    Related independent coverage: #27059 tests OTLP HTTP log and trace event
    export.
    
    The `codex-exec-server` bounded service tag now stays with the
    exec-server adoption change instead of this reusable infrastructure
    stack.
    
    ## Validation
    
    - `just test -p codex-otel`
    - `just fix -p codex-otel`
    - `just fmt`
  • Use cached remote plugin catalog for plugin list (#26932)
    ## Summary
    
    This changes the default remote plugin marketplace listing to use the
    cached global remote catalog when it is already present on disk. The
    foreground `plugin/list` response can then return from the local catalog
    cache instead of waiting on `/ps/plugins/list`.
    
    When a cached global catalog was present at the start of the request,
    `plugin/list` still schedules a background refresh through the existing
    plugin-list background task path so the disk cache is updated for future
    requests. Cache misses keep the existing synchronous remote fetch path
    and write the cache, and they do not schedule an extra duplicate
    background `/ps/plugins/list` refresh.
    
    Installed/enabled state continues to come from the existing remote
    installed overlay path. This change only affects the global remote
    catalog directory data used by `plugin/list`.
    
    ## Testing
    
    - `just fmt`
    - `just test -p codex-app-server
    plugin_list_uses_cached_global_remote_catalog_and_refreshes_it`
    - `just test -p codex-core-plugins`
    - `git diff --check`