Commit Graph

3616 Commits

  • core: resize all history images behind a feature flag (#27247)
    ## Summary
    
    Adds complete client-side image preparation behind the default-off
    `resize_all_images` feature flag.
    
    When enabled, local image producers defer decoding and resizing. Images
    are prepared centrally before insertion into conversation history,
    covering user input, `view_image`, and structured tool-output images.
    
    ## Behavior
    
    - Processes base64 `data:` images in messages and function/custom tool
    outputs.
    - Leaves non-data URLs, including HTTP(S) URLs, unchanged.
    - Applies image-detail budgets:
      - `high` and omitted: 2048px maximum dimension and 2.5K 32px patches.
      - `original`: 6000px maximum dimension and 10K 32px patches.
      - `auto`: uses the same 2048px / 2.5K-patch budget as high.
      - `low`: unsupported and replaced with an actionable placeholder.
    - Preserves original image bytes when no resize or format conversion is
    needed.
    - Enforces the shared 1 GiB encoded and decoded data-URL sanity limits.
    - Replaces only an image that fails preparation, preserving sibling
    content and tool-output metadata.
    - Uses bounded placeholders distinguishing generic processing failures,
    oversized images, and unsupported `low` detail.
    - Prepares resumed and forked history before installing it as live
    history without modifying persisted rollouts.
    
    ## Flag-Off Behavior
    
    When `resize_all_images` is disabled:
    
    - Existing local user-input and `view_image` processing remains
    unchanged.
    - Existing decoding and error behavior remains unchanged.
    - Arbitrary tool-output images are not processed.
    - HTTP(S) image URLs continue to be forwarded unchanged.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/27245
    - 👉 `2` https://github.com/openai/codex/pull/27247
    -  `3` https://github.com/openai/codex/pull/27246
    -  `4` https://github.com/openai/codex/pull/27266
  • core: cache turn diff rendering (#27489)
    ## Summary
    
    Turn diff updates repeatedly rendered and serialized the entire
    accumulated diff after every `apply_patch`. The event path also rendered
    once before updating the tracker solely to test whether a diff existed.
    In production feedback CODEX-20PW, 2,589 patches across 72 paths
    produced 401 notifications totaling 441 MB, with the hottest paths
    patched 518 and 495 times.
    
    This change:
    
    - replaces the pre-update render with a cheap cached-state check
    - caches each rendered file diff by path and content revision, so an
    update only invokes Myers for affected paths
    - caches the deterministic aggregate diff so event emission and turn
    completion reuse it without recomputation
    - preserves invalidation and net-zero clear notifications
    - applies a 100 ms per-file `similar` timeout; ordinary files complete
    far below this threshold, while pathological rewrites fall back to a
    coarse unified hunk that still represents the exact final contents
    
    The 100 ms deadline bounds synchronous tool-completion latency while
    leaving substantial headroom for normal diffs. The regression test
    applies the fallback diff through the repository's patch parser and
    verifies byte-for-byte final contents.
    
    ## Validation
    
    - `cargo test -p codex-core turn_diff_tracker::tests` (14 passed)
    - `cargo test -p codex-core tools::events::tests` (4 passed)
    - `just fix -p codex-core`
    - `just fmt`
    
    Focused coverage verifies that 42 updates across two files perform 42
    file renders rather than repeatedly rendering the accumulated set,
    unchanged paths are not re-diffed, clear events remain correct, and a
    48,000-line near-total rewrite returns promptly and applies to the exact
    expected result. The full `codex-core` suite was not used as the final
    gate because an unrelated existing multi-agent test hit a stack overflow
    when run during investigation.
    
    ## Bug context
    
    - Sentry feedback: CODEX-20PW
    - Correlation IDs: `019eb2a9-13d2-74e0-b690-27ee224ffb6d`,
    `019e9ad7-09c3-7cb2-b728-ee3acba103ab`
  • 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
  • [codex] Preserve disabled MCP servers across runtime overlays (#27414)
    ## Why
    
    Recent MCP runtime overlay changes replace same-name configured server
    entries with compatibility or extension-provided configs. Those
    replacement configs default to enabled, so an MCP server explicitly
    configured with `enabled = false` could be initialized anyway.
    
    The connection manager still filters disabled servers correctly, but the
    configured disabled state was lost before initialization reached that
    filter.
    
    ## What changed
    
    - Remember MCP servers that are disabled in the configured view before
    applying runtime fallbacks and extension overlays.
    - Restore `enabled = false` for those servers after overlays, while
    leaving all other overlay fields and `Remove` precedence unchanged.
    - Add focused extension-backed regression coverage for a disabled
    `codex_apps` server.
    
    ## Testing
    
    - `just fmt`
    - `just test -p codex-mcp-extension`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp-extension`
    
    The full workspace `just test` suite was not run.
  • [codex] Skip local curated discovery for remote plugins (#27311)
    ## Summary
    
    - skip the local `openai-curated` marketplace before marketplace loading
    when tool-suggest discovery uses remote plugins
    - preserve existing marketplace listing behavior for all other callers
    and when remote plugins are disabled
    - add regression coverage proving the curated marketplace is excluded
    before its malformed manifest can be read
    
    ## Why
    
    Tool-suggest discovery previously loaded every local `openai-curated`
    plugin manifest and only discarded that marketplace afterward when
    remote plugins were enabled. The remote catalog is used in that mode, so
    the local scan consumed CPU without contributing discoverable plugins.
    
    ## Impact
    
    Remote-plugin tool suggestion discovery no longer reads the local
    curated marketplace and its plugin manifests. `openai-bundled`,
    configured marketplaces, normal `plugin/list` behavior, and local
    curated discovery when remote plugins are disabled are unchanged.
    
    ## Validation
    
    - `just test -p codex-core-plugins
    list_marketplaces_can_skip_openai_curated_before_loading`
    - `just test -p codex-core
    list_tool_suggest_discoverable_plugins_omits_openai_curated_when_remote_enabled`
    - `just fmt`
    - `git diff --check`
  • Guard core test subprocess cleanup (#27343)
    ## Why
    
    Local integration-heavy `codex-core` CLI tests can time out or be
    interrupted after spawning `codex exec`. Stopping only the direct child
    is not enough: `codex exec` can leave grandchildren behind, including
    `python3`/`python3.12` processes that get reparented to PID 1 and keep
    running after the test is gone.
    
    This PR fixes that failure mode directly for the affected CLI
    integration tests, without changing production code or reducing local
    test concurrency.
    
    ## What
    
    - Run the `cli_stream` `codex exec` subprocesses through a small private
    wrapper in `core/tests/suite/cli_stream.rs`.
    - Spawn those subprocesses in their own process group before execution.
    - Keep `.output()`-style stdout/stderr capture and the existing
    30-second timeout behavior.
    - Own each spawned process with a drop guard that kills the whole
    process group on success, timeout, panic, or other early return.
    
    The switch from `assert_cmd::Command` to `std::process::Command` is only
    for these subprocess launches; `assert_cmd` does not expose a pre-spawn
    hook for setting the process group.
    
    ## Verification
    
    - `just test -p codex-core --test all responses_mode_stream_cli`
    
    This is limited to core integration tests; it does not change production
    `src` code paths.
  • [plugins] Inject remote_plugin_id into install elicitations (#26409)
    Summary
    - Propagate cached remote plugin IDs through Codex plugin discovery.
    - Inject `remote_plugin_id` and connector IDs into
    `request_plugin_install` elicitation `_meta` from the resolved plugin.
    - Keep the remote plugin ID out of the model-facing tool schema,
    arguments, and result.
    
    Validation
    - `just test -p codex-tools`
    - `just test -p codex-core-plugins`
    - `just test -p codex-core
    list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins`
    - `just fix -p codex-tools`
    - `just fix -p codex-core-plugins`
    - `just fix -p codex-core`
    - `git diff --check`
    - `just test -p codex-core` was also attempted: 2,581 passed, 55 failed,
    and 1 timed out across unrelated sandbox/environment-sensitive
    integration tests.
  • [codex] Retry transient Guardian review failures (#27062)
    ## Background
    
    Codex can use **Auto Review** for permission requests. Instead of asking
    the user immediately, Codex starts a separate locked-down reviewer
    session called **Guardian**, which returns a structured `allow` or
    `deny` assessment.
    
    The Guardian reviewer is itself a Codex session, so its model request
    can fail for transient infrastructure reasons such as model overload,
    HTTP connection failure, or response-stream disconnect. Today, any such
    failure immediately ends the Auto Review attempt and blocks the action.
    
    This PR adds bounded retries for failures that the existing protocol
    explicitly identifies as transient.
    
    Linear context:
    [CA-539](https://linear.app/openai/issue/CA-539/retry-auto-review-infrastructure-failures-and-fall-back-to-manual)
    
    ## What changes
    
    A Guardian review can now make at most **three total attempts**:
    
    1. Run the review normally.
    2. Retry after a jittered delay of roughly 180–220 ms if the first
    attempt fails with an eligible error.
    3. Retry after a jittered delay of roughly 360–440 ms if the second
    attempt also fails with an eligible error.
    
    All attempts share the original review deadline. Jitter spreads retries
    from concurrent clients to reduce synchronized load during broader
    outages. The retries do not reset the user's maximum wait time, and the
    backoff waits terminate early if the review is cancelled or the deadline
    expires.
    
    Before retrying, the existing Guardian session lifecycle decides whether
    the session remains usable. Healthy trunks are reused, broken trunks are
    removed by the existing cleanup path, and ephemeral sessions continue to
    clean themselves up.
    
    The review still emits one logical lifecycle to clients. Recoverable
    intermediate failures do not produce warnings or terminal events.
    
    ## Retry policy
    
    ### Retried up to twice
    
    - model/server overload
    - HTTP connection failure
    - response-stream connection failure
    - response-stream disconnect
    - internal server error
    - a final reviewer message that cannot be parsed as the required
    Guardian assessment
    
    ### Not retried
    
    - bad or invalid requests
    - authentication failures
    - usage limits
    - cyber-policy failures
    - errors without a structured category
    - a request that already exhausted the lower-level Responses retry
    budget
    - a completed Guardian turn with no assessment payload
    - prompt-construction failures
    - Guardian review timeout
    - cancellation or abort
    - a valid `deny` assessment
    
    The session-error classification uses `ErrorEvent.codex_error_info`; it
    does not inspect error-message strings.
    
    ## Implementation notes
    
    - `wait_for_guardian_review` preserves the complete `ErrorEvent`,
    including structured `codex_error_info`.
    - Guardian session failures preserve the original message and optional
    structured `CodexErrorInfo`.
    - The retry policy classifies the explicitly transient `CodexErrorInfo`
    variants; unknown, absent, and deterministic categories are not retried.
    - The Guardian session manager receives the caller's deadline rather
    than creating a new timeout per attempt.
    - Analytics record the final `attempt_count`.
    - Retry orchestration does not add a separate session-cleanup protocol;
    it relies on the existing trunk and ephemeral lifecycle decisions.
    
    ## Automated testing
    
    Focused Guardian coverage verifies:
    
    - every supported transient `CodexErrorInfo` is classified as retryable,
    while absent and non-transient categories are not;
    - structured transient session failure -> retry -> approval with the
    healthy trunk reused;
    - two invalid Guardian responses -> third attempt -> approval, with
    exactly three requests;
    - three invalid responses -> existing fail-closed result, with exactly
    three requests and one terminal lifecycle;
    - valid denial, missing payload, invalid request, timeout, cancellation,
    and prompt/session construction failures are not retried;
    - retry eligibility ends after the third attempt;
    - retry delays use the shared exponential backoff helper and remain
    within the expected jitter bounds;
    - cancellation and deadline expiry interrupt the backoff wait;
    - healthy trunks are reused across retryable failures;
    - broken event streams remove the trunk through the existing lifecycle
    cleanup;
    - an ephemeral retry does not disturb a concurrent trunk review.
    
    Validation performed:
    
    - `just test -p codex-core guardian_review_
    guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history
    run_review_removes_trunk_when_event_stream_is_broken` — **42 passed**;
    - `just test -p codex-analytics` — **71 passed**;
    - scoped Clippy fixes for `codex-core` and `codex-analytics` passed.
    
    A prior full `codex-core` run had unrelated environment-sensitive
    failures outside Guardian coverage.
    
    ## Manual QA
    
    The focused integration tests use the local mock Responses server to
    inspect exact request counts and emitted lifecycle events. They confirm
    that retries are internal, a successful later attempt supplies the final
    decision, non-retryable failures issue only one request, and exhausted
    retries emit only one terminal result.
  • 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.
  • Add app-server background terminal process APIs (#26041)
    ## Summary
    
    Codex Apps needs app-server as the source of truth for chat-started
    background terminals instead of guessing from local process trees.
    
    This PR adds experimental v2 APIs to list and terminate background
    terminals for a loaded thread using app-server process ids, so clients
    can manage background terminals without local PID discovery.
    
    ## Changes
    
    - `thread/backgroundTerminals/list` returns paginated background
    terminal records with `itemId`, app-server `processId`, `command`,
    `cwd`, nullable `osPid`, nullable `cpuPercent`, and nullable `rssKb`.
    - `thread/backgroundTerminals/terminate` terminates one running
    background terminal by app-server `processId` and returns whether a
    process was terminated.
    - Background terminal list and terminate operations use unified-exec
    process manager state as their source of truth.
  • [codex] Remove async_trait from ToolExecutor (#27304)
    ## Why
    
    We're now [discouraging use of
    `async_trait`](https://github.com/openai/codex/pull/20242).
    
    Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
    debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.
    
    Stacked on #27299, this PR applies the trait change after the handler
    bodies have been outlined.
    
    ## What
    
    Changed `ToolExecutor::handle` to return an explicit boxed
    `ToolExecutorFuture` instead of using `async_trait`.
    
    Updated ToolExecutor implementors to return `Box::pin(...)`, reexported
    the future alias through `codex-tools` and `codex-extension-api`, and
    removed `codex-tools` direct `async-trait` dependency.
  • [codex] Outline ToolExecutor handler bodies (#27299)
    ## Why
    
    We're now [discouraging use of
    `async_trait`](https://github.com/openai/codex/pull/20242).
    
    Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
    debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.
    
    For ease of reviewing, this is a prefactor to extract trait method
    implementations to inherent methods. This will prevent changing
    indentation from creating a huge diff.
    
    ## What
    
    Outlined existing `ToolExecutor::handle` bodies into inherent async
    `handle_call` methods across core and extension tool handlers.
    
    The trait methods still use `async_trait` and now delegate to
    `self.handle_call(...).await`; handler behavior is unchanged.
  • [codex] Store compact window id in rollout (#27264)
    ## Why
    
    Compaction window identity is part of session history, not model-client
    transport state. Persisting it with the compacted rollout item lets
    resumed threads continue from the reconstructed window without keeping
    mutable window state on `ModelClient`.
    
    ## What changed
    
    - Added `window_id` to `CompactedItem` and stamp it when
    `replace_compacted_history` installs compacted history.
    - Moved auto-compact window id ownership into `AutoCompactWindow` /
    `SessionState`; `ModelClient` now receives the request window id from
    callers instead of storing it.
    - Returned `window_id` from rollout reconstruction for resume.
    Reconstruction uses the newest surviving compacted item's stored
    `window_id` when present, and falls back to the legacy compacted-item
    count when it is absent.
    - Kept fork startup at the fresh default window id and updated direct
    model-client tests to pass explicit test window ids.
    
    ## Validation
    
    - `cargo check -p codex-core --tests`
  • Use latest-wins MCP manager replacement (#27259)
    ## Summary
    
    We originally addressed startup prewarming holding the read side of
    `RwLock<McpConnectionManager>` by snapshotting tool-list state. Review
    feedback identified the broader ownership problem: the outer
    synchronization should only publish or retrieve the current manager,
    while MCP operations rely on the manager's internal synchronization. A
    follow-up preserved operation retirement with a separate gate, but
    further review questioned whether that synchronization was actually
    required and whether we could support latest-wins replacement instead.
    
    This PR now stores the current MCP manager in `ArcSwap`. Each operation
    uses `load_full()` to obtain an owned `Arc<McpConnectionManager>`, then
    performs MCP I/O without retaining the publication mechanism. Refresh
    cancels obsolete startup work, constructs a replacement, and atomically
    publishes it. New operations see the latest manager, while operations
    that already loaded the previous manager retain a valid handle. Refresh
    happens at a turn boundary, so there should be no active user tool calls
    to drain.
    
    Git history supports dropping the outer `RwLock`. It was introduced in
    `03ffe4d595` on November 17, 2025 for non-blocking MCP startup: the
    session published an empty manager, startup initialized that same object
    while holding the write lock, and readers waited for initialization.
    `7cd2e84026` on February 19, 2026 removed that two-phase initialization
    in favor of constructing a fresh manager and swapping it in, explicitly
    noting that `Option` or `OnceCell` could replace the placeholder design.
    Hot reload later reused the existing lock to publish a replacement, but
    I found no indication that the lock was introduced to guarantee
    in-flight tool calls finish before refresh or shutdown.
    
    Terminal shutdown remains separate from refresh: it aborts startup
    prewarming and active tasks before shutting down the current manager, so
    tool calls may be interrupted and no model WebSocket work continues
    after shutdown. Focused regression coverage exercises pending tool-list
    cancellation, deferred refresh, and startup-prewarm shutdown.
  • 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.
  • [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
  • 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
  • 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] 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.
  • [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`
  • 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.
  • 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`
  • [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`
  • Pair thread environment settings (#26687)
    ## Why
    
    Thread cwd and environment selections are a single logical setting in
    core: updating one without the other can silently desynchronize the
    next-turn execution context. This change makes that relationship
    explicit in the internal thread settings flow while preserving the
    existing app-server public API shape.
    
    ## What changed
    
    - Moved the cwd/environment pair through internal
    `ThreadSettingsOverrides.environment_settings` instead of a top-level
    internal `cwd` field.
    - Kept `thread/settings/update` public params unchanged, with app-server
    translating top-level `cwd` into the paired internal settings shape.
    - Moved `Op::UserInput` environment overrides into thread settings so
    user turns and settings updates use the same core path.
    - Updated core, app-server, MCP, memories, sample, and test callsites to
    construct the paired settings shape.
    
    ## Verification
    
    - `git diff --check`
    - Local test run starting after PR creation.
  • [codex] Calm multi-agent v2 usage prompts (#27037)
    ## Summary
    - tighten the default multi-agent v2 root and subagent usage hints to
    bias toward local work
    - add a pre-call gate to the v2 spawn_agent description for independent,
    bounded, parallelizable subtasks
    
    ## Validation
    - just fmt
    - started just test -p codex-core, but it was interrupted before
    completion per follow-up request to commit and push immediately
  • fix: preserve auto review across config and delegation (#26230)
    ## Why
    
    Auto Review should remain the effective approval reviewer when settings
    cross runtime boundaries. A config or app-server round trip must not
    change the reviewer identity, and delegated work must not silently fall
    back to user review.
    
    This requires both a stable canonical serialized value and propagation
    of the effective setting. `auto_review` is the canonical value across
    protocol and app-server output, while `guardian_subagent` remains
    accepted as backward-compatible input.
    
    ## What changed
    
    - serialize `ApprovalsReviewer::AutoReview` consistently as
    `auto_review` across core protocol and app-server v2
    - continue accepting `guardian_subagent` when reading existing config or
    client requests
    - carry the active turn's approval reviewer into spawned agents
    - update config/debug expectations and add delegated-task regression
    coverage
    
    ## Scope
    
    This does not change Guardian policy or remove compatibility with
    existing `guardian_subagent` inputs. It preserves the selected reviewer
    across serialization, config reloads, app-server settings, and delegated
    task setup.
    
    Related Guardian changes are split independently:
    
    - #26231 adds denials and soft denials
    - #26334 retries transient reviewer failures
    - #26333 reuses narrowly scoped low-risk approvals
    - #26232 adds TUI denial recovery
    
    ## Validation
    
    - `just test -p codex-app-server-protocol` (224 passed)
    - regression coverage for delegated task reviewer propagation
    - serialization coverage for canonical `auto_review` output and legacy
    `guardian_subagent` input
    
    ---------
    
    Co-authored-by: saud-oai <saud@openai.com>
  • [codex-analytics] report compaction analytics details (#26680)
    ## Why
    
    Compaction analytics adds retained image count and compaction summary
    output tokens for v1.5 specifically.
    
    ## What changed
    
    - Add nullable `retained_image_count` and `compaction_summary_tokens`
    fields to `codex_compaction_event`.
    - Populate them only for `responses_compaction_v2`: retained images come
    from the retained v2 compacted history, and summary tokens come from
    `response.completed.token_usage.output_tokens`.
    - Leave local and legacy remote compaction events as `null` for these
    detail fields.
    
    ## Verification
    
    - `just fmt`
    - `just fix -p codex-core`
    - `just test -p codex-core
    build_v2_compacted_history_counts_retained_input_images`
    - `git diff --check`
  • Add HTTP window ID to Responses client metadata (#26923)
    ## Summary
    
    - Keep the existing `x-codex-window-id` HTTP header unchanged.
    - Also send the same window ID in Responses `client_metadata`, allowing
    supported backend paths to surface it as
    `x-client-meta-x-codex-window-id`.
    - Cover normal HTTP Responses and remote compaction v2 requests without
    changing window generation or compaction behavior.
    
    ## Why
    
    In the `2026-06-06T23` production hour, all 28,729 HTTP compaction
    requests had `window_id` in `x-codex-turn-metadata`, but only 73
    retained the direct `x-codex-window-id` header. The request-body
    `client_metadata` path is already used for installation ID and is
    preserved through supported Responses API paths.
    
    This is additive metadata only. It does not change the direct header,
    request count, model input, compaction routing, window generation, or
    user response behavior.
    
    Legacy `/v1/responses/compact` is intentionally unchanged. Its current
    server-side `CompressBody` schema does not accept `client_metadata` and
    rejects unknown fields, so supporting that path requires a backend
    schema change before the Codex client can safely send this field.
    
    ## Validation
    
    - Current head: `219baef3c`, rebased onto `origin/main` at `26d932983`.
    - The post-rebase diff remains limited to the original five files (`22`
    insertions, `6` deletions); the legacy experiment remains fully
    reverted.
    - `just test -p codex-core
    responses_stream_includes_subagent_header_on_review`: passed; validates
    normal HTTP Responses metadata.
    - `just test -p codex-core
    remote_compact_v2_reuses_compaction_trigger_for_followups`: passed;
    validates remote compaction v2.
    - `just test -p codex-core
    remote_manual_compact_chatgpt_auth_reuses_service_tier_and_prompt_cache_key`:
    passed; validates that legacy compact keeps its accepted payload shape.
    - `just test -p codex-core
    remote_manual_compact_api_auth_omits_service_tier_and_reuses_prompt_cache_key`:
    passed; validates the legacy API-key payload as well.
    - `just fmt`: passed; an unrelated root `justfile` rewrite produced by
    the formatter was discarded.
    - `git diff --check origin/main...HEAD`: passed.
    
    The focused server pytest could not start in the local monorepo
    environment because test setup is missing the `dotenv` module. Server
    source and tests explicitly show that `CompressBody` omits
    `client_metadata` and `/v1/responses/compact` returns HTTP 400 for
    unknown body fields.
  • [codex] Exclude external tool output from memories (#26821)
    ## Summary
    
    - add contains_external_context() to tool output so other tools can be
    opted out of influencing memory when disable_on_external_context=true
    - Classify standalone web-search output as external context (to match
    behavior as hosted web search)
    - Verify with integration test
  • Avoid reopening v2 descendants on resume (#26997)
    ## Why
    
    Multi-agent v2 residency is intended to keep only the threads that need
    to be live. The existing rollout resume path still walked persisted open
    descendants and reopened the entire descendant tree when resuming a v2
    root, which turns resume into an eager reload of work that should stay
    unloaded until it is explicitly needed.
    
    The interrupted-agent path has a related residency issue. Interrupted
    agents remain open by design, so an idle interrupted resident should be
    eligible for eviction just like an idle completed or errored resident.
    Otherwise a resident set full of interrupted agents can consume every v2
    slot and block later spawns or reloads with `AgentLimitReached`.
    
    ## What Changed
    
    - Return early from `resume_agent_from_rollout` after resuming a v2
    thread so persisted v2 descendants are not reopened eagerly.
    - Treat idle `Interrupted` v2 residents as unloadable in the LRU
    residency path.
    - Add focused coverage for v2 root resume leaving descendants unloaded
    and for eviction of an idle interrupted v2 resident when a new slot is
    needed.
    
    ## Verification
    
    Added targeted `codex-core` tests covering:
    
    - v2 root resume with persisted descendants, verifying only the root is
    loaded after resume.
    - residency eviction of an idle interrupted v2 agent when the resident
    set is full.
  • Rename multi-agent v2 close_agent to interrupt_agent (#26994)
    ## Why
    
    `close_agent` is the wrong model-facing name for the v2 operation after
    the residency changes. V2 agents remain reusable by task name, and
    residency/unloading owns capacity management; the exposed tool should
    describe the action it actually performs: interrupt the target agent's
    current turn without making the agent unavailable for future messages or
    follow-up tasks.
    
    ## What changed
    
    - Rename the multi-agent v2 tool from `close_agent` to
    `interrupt_agent`.
    - Keep the v1 `close_agent` surface unchanged.
    - Update the v2 handler to send `Op::Interrupt`, keep interrupted agents
    registered, and reject root/self targets with interrupt-specific errors.
    - Route interrupt delivery through the existing dead-thread cleanup path
    so stale resident entries do not keep consuming capacity.
    - Update tool planning and handler tests for the new v2 surface and
    semantics.
    
    ## Verification
    
    Added focused coverage in:
    
    - `core/src/tools/spec_plan_tests.rs`
    - `core/src/tools/handlers/multi_agents_tests.rs`
  • feat: count V2 concurrency by active execution (#26969)
    ## Why
    
    Multi-Agent V2 concurrency should count active non-root turns, not
    resident or durable agent threads. The limit is intentionally best
    effort: admission checks are synchronous, but concurrent successful
    checks may overshoot slightly.
    
    ## What changed
    
    - Keep one root-derived execution limit on the shared `AgentControl`.
    - Count active V2 subagent turns with an RAII guard owned by
    `RunningTask`.
    - Check capacity before spawning or starting an idle agent, including
    direct app-server `turn/start` submissions.
    - Preserve queued delivery for agents that are already running.
    - Exempt automatic idle continuations so `/goal` work is not dropped
    when capacity is temporarily full.
    - Keep root and V1 turns outside this limiter.
    
    ## Test coverage
    
    - `execution_guards_count_active_v2_subagent_turns`
    - `execution_guards_ignore_root_and_v1_turns`
    - `v2_nested_spawn_checks_shared_active_execution_capacity`
  • feat: add v2 agent residency lru (#26632)
    ## Why
    
    Multi-agent v2 treats agents as durable logical agents, not just live
    entries in `ThreadManager`. After the reload-on-delivery change, a v2
    agent can be addressed even if its thread is not currently loaded.
    
    This PR adds the next layer: loaded v2 subagents can be paged out of
    `ThreadManager` when the session has too many resident agents. That
    keeps residency separate from logical identity and prepares the stack
    for making v2 concurrency count active execution instead of existing
    agents.
    
    ## What Changed
    
    - Add an `AgentControl`-scoped LRU for resident v2 subagents.
    - Reserve residency before spawning or reloading a v2 subagent.
    - If resident capacity is full, unload the least-recently-used idle v2
    subagent from `ThreadManager`.
    - Keep `ThreadManager` as a primitive loaded-thread store; it does not
    own the LRU policy.
    - Keep unloaded agents registered and durable so they can be reloaded by
    the delivery path.
    - Preserve the existing v2 cap semantics by using the derived non-root
    v2 cap for residency.
    
    Eviction is intentionally conservative. A thread is unloadable only when
    it is a v2 subagent, has completed or errored, has no active turn, and
    has no pending mailbox work. Before removal, the rollout is materialized
    and flushed.
    
    ## Assumptions And Non-Goals
    
    - PR #26623 provides the reload-on-delivery path for unloaded v2 agents.
    - `ThreadManager` membership means loaded/resident, not logical agent
    existence.
    - `AgentRegistry` remains the logical identity/metadata source for v2
    agents that may be unloaded.
    - `list_agents` remains a recent/resident view for now.
    - This does not change active execution concurrency; that is the next
    PR.
    - This does not change `close_agent` semantics.
    - This does not change or remove `resume_agent`.
    - This does not add a new residency config knob.
    
    ## Stack
    
    1. V2 durable lookup and reload on delivery (#26623) - reload unloaded
    v2 agents before delivering follow-up/input.
    2. V2 residency LRU (this PR) - unload idle resident v2 agents from
    `ThreadManager` when resident capacity is full.
    3. V2 active-execution concurrency - count running non-root v2 turns
    instead of logical agents.
    4. V2 close/interrupt semantics - make v2 close interrupt the current
    turn without deleting durable identity.
    5. V2 resume cleanup - remove the manual resume surface for v2 while
    keeping internal reload support.
    
    ## Validation
    
    - Added focused coverage for the residency LRU eviction path.
    - Local clippy/check/tests were not run; CI will cover them.
  • [codex] Enable standalone web search in code mode (#26719)
    ## What
    
    - Consume plaintext `output` from standalone search while retaining
    optional `encrypted_output` parsing.
    - Expose `web.run` to code mode and return search output to nested
    JavaScript calls.
    - Cover direct and code-mode standalone search paths with integration
    tests.
    
    ## Why
    
    `/v1/alpha/search` now returns plaintext output, which code mode needs
    to consume standalone search results.
    
    ## Test plan
    
    - `just test -p codex-api`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-core code_mode_can_call_standalone_web_search`
    - `just test -p codex-app-server
    standalone_web_search_round_trips_output`
  • fix: preserve approval sandbox decisions in unified exec (#24981)
    ## Why
    
    This PR fixes approval sandbox semantics in the unified-exec path. The
    zsh-fork runtime exposed the bug because the shell can do meaningful
    work before any intercepted child `execv(2)` exists: redirections,
    builtins, globbing, and pipeline setup all happen in the launch process.
    If the model requested `sandbox_permissions=require_escalated`, or an
    exec-policy `allow` rule explicitly bypassed the sandbox, that approved
    sandbox decision needs to be preserved for the launch path and for
    intercepted execs that use the same approval machinery.
    
    The behavior is not only about zsh fork. The production changes are in
    shared approval/escalation code, so they also affect non-zsh-fork
    intercepted exec paths that go through the same sandbox decision logic.
    The narrow intent is to preserve the approval decision while still
    keeping denied-read profiles and bounded additional-permission requests
    sandboxed.
    
    ## Production Changes
    
    - `codex-rs/core/src/tools/runtimes/unified_exec.rs`: derives a
    `launch_sandbox_permissions` value from the requested sandbox
    permissions and the runtime filesystem policy, then uses that value for
    managed-network/env setup and launch sandbox selection. This keeps full
    approval or policy-bypass decisions visible to the first unified-exec
    attempt, while still preventing a full sandbox override from discarding
    denied-read restrictions. Direct unified exec keeps the same decision
    surface; the important difference is that zsh-fork launch setup no
    longer accidentally loses the approved parent sandbox decision.
    
    - `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs`: makes
    intercepted-exec escalation selection explicit for the three sandbox
    permission modes. `UseDefault` only escalates when an exec-policy
    decision allows sandbox bypass, `RequireEscalated` escalates when
    unsandboxed execution is allowed, and `WithAdditionalPermissions`
    escalates through the bounded additional-permissions path instead of
    being treated as a full unsandboxed override. Unsandboxed intercepted
    execs now also rebuild the environment as `RequireEscalated`, which
    strips managed-network proxy variables consistently with other
    unsandboxed execution.
    
    ## Test Coverage
    
    Most of the PR is tests. The new coverage verifies:
    
    - unified exec preserves parent approval and exec-policy sandbox
    decisions for zsh-fork launch selection;
    - bounded `with_additional_permissions` remains sandboxed and
    permission-profile based;
    - denied-read profiles are not weakened by parent approval;
    - explicit prompt rules still prompt for intercepted execs after the
    parent command is approved;
    - unsandboxed intercepted execs strip managed-network env vars.
    
    No documentation update is needed; this is an internal approval/sandbox
    correctness fix.
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/24981).
    * #24982
    * __->__ #24981
  • permissions: enforce managed permission profile allowlists (#24852)
    ## Why
    
    Permission profile allowlists are an enterprise security boundary, but
    they also need to compose across the managed requirements layers added
    in #24620.
    
    A map representation lets each requirements layer add, allow, or revoke
    individual profiles without replacing an entire array.
    
    ## Managed Contract
    
    Administrators configure the mergeable allow map with
    `allowed_permission_profiles`. A recommended enterprise configuration
    explicitly lists every built-in and custom profile users should be able
    to select:
    
    ```toml
    default_permissions = "review_only"
    
    [allowed_permission_profiles]
    ":read-only" = true
    ":workspace" = true
    review_only = true
    # ":danger-full-access" is intentionally omitted, so it is denied.
    
    [permissions.review_only]
    extends = ":read-only"
    ```
    
    - Profiles whose effective merged value is `true` are allowed.
    - Missing profiles and profiles set to `false` are denied.
    - This is a closed allowlist: built-in profiles and profiles introduced
    in future versions are denied unless explicitly allowed.
    - Explicitly list each built-in profile the enterprise wants to make
    available. Omit built-ins such as `:danger-full-access` when they should
    remain unavailable.
    - Set `default_permissions` explicitly to the allowed profile users
    should receive when they have no local selection.
    - Higher-precedence layers override only the profile keys they define.
    - `false` is only needed when a higher-precedence layer must revoke a
    `true` inherited from a lower layer.
    - Explicit keys must refer to known built-in or managed profiles.
    
    A custom or narrowed allowlist requires an allowed
    `default_permissions`. For compatibility, if both `:workspace` and
    `:read-only` are explicitly allowed, an omitted default resolves to
    `:workspace`; customer configurations should still set the intended
    default explicitly.
    
    When `allowed_permission_profiles` is absent, existing implicit
    permission and legacy `sandbox_mode` behavior is unchanged.
    
    ## What Changed
    
    - Add `allowed_permission_profiles` as a `BTreeMap<String, bool>` that
    merges per profile across requirements layers.
    - Enforce managed defaults, strict denial of omitted profiles, and the
    explicitly allowed standard-pair fallback.
    - Expose `allowedPermissionProfiles` through `configRequirements/read`
    and regenerate its schemas.
    - Add regression coverage for map composition and revocation, managed
    defaults, strict denial of omitted built-ins, and API output.
    
    ## Verification
    
    - Focused `codex-config` coverage for layered map composition and
    revocation
    - Focused `codex-core` coverage for managed defaults, invalid defaults,
    strict denial of omitted built-ins, and the standard built-in pair
    - Focused `codex-app-server` coverage for requirements API output
    - Scoped Clippy for `codex-config`, `codex-core`,
    `codex-app-server-protocol`, and `codex-app-server`
    
    ## Documentation
    
    The managed `requirements.toml` documentation should introduce
    `allowed_permission_profiles` as a closed permission-profile allowlist
    before this setting is published on developers.openai.com.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Send Responses Lite transport header (#26542)
    ## Summary
    
    - send `X-OpenAI-Internal-Codex-Responses-Lite: true` on HTTP Responses
    requests and WebSocket upgrade requests when model metadata enables
    Responses Lite
    - use client metadata when sending it over the websocket
    
    This PR is stacked on #26490.
    
    ## Why
    
    The Responses Lite marker is request-scoped for HTTP but
    connection-scoped for Responses-over-WebSocket because it is carried on
    the upgrade request. Reusing a cached socket opened for the opposite
    mode would therefore send the wrong transport contract.
    
    ## Validation
    
    - `just test -p codex-core responses_lite`
    - `just test -p codex-core
    responses_websocket_reconnects_when_responses_lite_mode_changes`
    - `just fix -p codex-core`
    - `just fmt`
  • [codex-rs] support v2 personal access tokens (#25731)
    ## Summary
    
    - add v2 personal access token support for `codex login
    --with-access-token` and `CODEX_ACCESS_TOKEN`
    - classify opaque `at-` tokens separately from legacy Agent Identity
    JWTs
    - hydrate required ChatGPT account metadata through AuthAPI
    `/v1/user-auth-credential/whoami`
    - use PATs directly as bearer tokens while preserving existing ChatGPT
    account surfaces
    - expose PAT-backed auth as the explicit `personalAccessToken`
    app-server auth mode
    
    ## Implementation
    
    PAT auth is intentionally small and stateless. Loading a PAT performs
    one AuthAPI metadata request, stores the hydrated metadata in the
    in-memory auth object, and redacts the secret from debug output. Legacy
    Agent Identity JWT handling remains unchanged. The shared access-token
    classifier lives in a private neutral module because it dispatches
    between both credential types.
    
    PAT hydration fails closed when AuthAPI omits any required metadata,
    including email. Hydrated metadata is intentionally not persisted:
    startup performs a live `whoami` preflight so revoked tokens or changed
    account metadata are not accepted from a stale cache.
    
    ## Workspace restriction scope
    
    This change intentionally does **not** apply
    `forced_chatgpt_workspace_id` to PAT authentication. The setting is a
    client-side config guardrail, not an authorization boundary, and PAT
    does not currently require workspace-ID parity. The PAT login and
    `CODEX_ACCESS_TOKEN` paths therefore validate through AuthAPI without
    threading workspace-restriction state through access-token loading.
    Existing workspace checks for non-PAT auth remain on their established
    paths.
    
    ## App-server compatibility
    
    The public app-server `AuthMode` is shared across v1 and v2, and
    PAT-backed auth reports `personalAccessToken` through both APIs.
    Following human review, this intentionally removes the temporary v1
    compatibility mapping that reported PATs as `chatgpt`; the deprecated v1
    API is kept in parity with v2 rather than maintaining a separate closed
    enum. Clients with exhaustive auth-mode handling in either API version
    must add the new case and should generally treat it as ChatGPT-backed
    unless they need PAT-specific behavior.
    
    The v1 auth-status response still omits the raw PAT when `includeToken`
    is requested because that response cannot carry the account metadata
    needed to reuse the credential safely. Persisted PAT auth also omits the
    new enum value so older Codex builds can deserialize `auth.json` and
    infer PAT auth from the credential field after a rollback.
    
    ## Validation
    
    Latest review-fix validation:
    
    - `CARGO_INCREMENTAL=0 just test -p codex-login` (126 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli` (263 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli
    stored_auth_validation_handles_personal_access_token`
    - `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol` (226
    passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-models-manager
    refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth`
    - `CARGO_INCREMENTAL=0 just test -p codex-tui
    existing_non_oauth_chatgpt_login_counts_as_signed_in`
    - `CARGO_INCREMENTAL=0 just fix -p codex-login -p
    codex-app-server-protocol -p codex-models-manager -p codex-tui -p
    codex-cli`
    - `just fmt`
    - `git diff --check`
    
    The broader `codex-tui` suite previously compiled and ran 2,834 tests.
    Three unrelated environment-sensitive guardian/IDE-socket tests failed
    after retries; the PAT-relevant TUI coverage passed.
  • [codex] Gate terminal visualization instructions in TUI (#26013)
    ## Summary
    - add `Feature::TerminalVisualizationInstructions` as
    `UnderDevelopment`, disabled by default
    - keep terminal visualization instructions inside the TUI package
    - append them to existing developer instructions for TUI start, resume,
    and fork flows only when enabled
    - intentionally do not apply them to `codex exec`
    
    ## Rollout
    Control behavior is unchanged. TUI dogfooders can enable
    `terminal_visualization_instructions`; no default user receives the new
    terminal-specific instructions.
    
    The shared visualization-selection rule is supplied separately through
    the `codex_proxy_model_3` Statsig layer for every target Codex model
    slug in the gated cohort. This TUI feature determines how to render an
    appropriate visualization on the terminal surface; the model-layer
    treatment determines when to use one.
    
    ## Validation
    - `cargo test -p codex-tui
    terminal_visualization_instructions_are_gated_for_all_tui_thread_flows
    --lib`
    - `cargo test -p codex-features --lib`
    - `cargo fmt --all -- --check`
    - `git diff --check`
    - GPT-5.4 and GPT-5.5 real prompt-pipeline smoke tests: both visualized
    the positive mapping case, abstained on the negative route case, and
    passed exact prompt-stack verification on CLI and App
    - refreshed onto current `main` with a clean merge and reran the focused
    validation
    
    The full 53-probe all-model treatment comparison and requested
    production coding evals remain rollout gates before broadening beyond
    the initial employee cohort.
    
    This PR remains open for normal human review.
  • [codex] Use standalone tools for Responses Lite (#26490)
    ## Summary
    
    Responses Lite does not execute hosted Responses tools, so models using
    it must route web search and image generation through Codex-owned
    executors & standalone Response's API endpoints.
    
    This PR is stacked on #26487.
    
    ## Validation
    
    - `cargo test -p codex-core responses_lite_ --lib`
    - `cargo test -p codex-core
    standalone_executors_remain_hidden_without_flags_or_responses_lite
    --lib`
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates --lib`
    - `cargo test -p codex-web-search-extension -p
    codex-image-generation-extension`
    - `cargo test -p codex-app-server --test all standalone_`
    - `cargo fmt --all -- --check`
  • [2 of 2] Finish moving goal runtime to extension (#26548)
    ## Stack
    
    1. [#26547](https://github.com/openai/codex/pull/26547) - [1 of 2] Align
    goal extension with core behavior
    2. [#26548](https://github.com/openai/codex/pull/26548) - [2 of 2] Move
    goal runtime to extension
    
    ## Why
    
    This PR completes the switch of the goal behavior to the
    extension-backed runtime and removes the old core goal implementation.
    
    ## What Changed
    
    - Installs the goal extension for app-server `ThreadManager` sessions.
    - Routes app-server thread goal `get`, `set`, and `clear` through
    `GoalService`.
    - Uses thread-idle lifecycle emission after goal resume and snapshot
    ordering so the extension can decide whether to continue the goal.
    - Forwards extension goal updates through a FIFO async app-server
    notification path so backpressure does not drop them or reorder updates.
    - Keeps review turns from enabling goal runtime behavior.
    - Plans extension tools before dynamic tools so built-in goal tool names
    keep their old precedence when goals are enabled.
    - Removes the old core goal runtime, core goal tool handlers, and core
    goal tool specs.
    - Updates tests that were coupled to the core-owned goal runtime while
    leaving the legacy `<goal_context>` compatibility path in core for old
    threads.
    - Removes the stale cargo-shear ignore now that `codex-goal-extension`
    is used by the workspace.
    - Keeps realtime event matching exhaustive after removing the old
    goal-specific realtime text path.
    
    
    ## Validation
    
    - Ran manual `/goal` runs in TUI. Validated time accounting matched
    wall-clock time and goal lifecycle state transitions.