Commit Graph

874 Commits

  • feat: use encrypted local secrets for MCP OAuth (#27541)
    ## Summary
    
    - store MCP OAuth credentials in the configured auth credential backend
    - support encrypted-local OAuth storage, including legacy keyring
    migration
    - propagate the credential backend through MCP refresh, session, CLI,
    and app-server paths
    
    ## Stack
    
    1. #27504 — config and feature flag
    2. #27535 — auth-specific secret namespaces
    3. #27539 — encrypted CLI auth storage
    4. this PR — encrypted MCP OAuth storage
    
    This is a parallel review stack; the original #17931 remains unchanged.
    
    ## Tests
    
    - `just test -p codex-rmcp-client` (the transport round-trip test passed
    after building the required `codex` binary and retrying)
    - `just test -p codex-mcp`
    - `just test -p codex-app-server
    refresh_config_uses_latest_auth_keyring_backend`
    - `just test -p codex-core
    refresh_mcp_servers_is_deferred_until_next_turn`
    - `just test -p codex-cli mcp`
    - `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
    -p codex-app-server -p codex-protocol`
    - `just bazel-lock-check`
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • Remove TUI realtime voice support (#27801)
    ## Why
    
    Removes the realtime audio support from TUI.
    
    ## What Changed
    
    - Removed the TUI `/realtime` and realtime `/settings` command paths.
    - Deleted TUI voice capture/playback, WebRTC session handling,
    audio-device selection UI, and recording-meter code.
    - Removed TUI realtime tests and snapshots that covered the deleted
    surfaces.
    - Dropped the TUI-only `cpal` and `codex-realtime-webrtc` dependencies
    and refreshed the Rust/Bazel locks.
  • [codex] Align implicit skill reads with parser (#27926)
    ## Summary
    - reuse the shared shell read parser for implicit skill doc invocation
    detection
    - add regression coverage for `nl -ba .../SKILL.md`
    
    ## Why
    Desktop could render `Read User Context skill` for reads recognized by
    the shared command parser, while implicit `skill_invocation` analytics
    used a separate reader allowlist and missed cases such as `nl`.
    
    ## Validation
    - `HOME=/private/tmp/codex-core-skills-home-pr
    PATH=/Users/alexsong/.cache/cargo-home/bin:$PATH
    CARGO_HOME=/Users/alexsong/.cache/cargo-home just test -p
    codex-core-skills`
    - `git diff --cached --check`
    - `just fmt` attempted; Rust formatting completed, but the Python
    formatters could not download uncached Ruff wheels because
    `files.pythonhosted.org` is blocked in this sandbox.
    - `bazel mod deps --lockfile_mode=update/error
    --repo_env=ASPECT_TOOLS_TELEMETRY= --repo_env=DO_NOT_TRACK=1` evaluated
    the module graph and produced no `MODULE.bazel.lock` diff, but Bazel
    crashed on sandboxed `sysctl` during exit.
  • [login] revoke existing auth before starting login (#27674)
    ## Why
    
    `codex login` previously persisted newly issued OAuth credentials and
    only then attempted to revoke the superseded refresh token. The old
    credential must be revoked before a replacement browser or device-code
    flow starts, and successful login must not perform any post-login
    revocation attempt.
    
    ## What changed
    
    - Revoke and clear existing stored auth before browser or device-code
    CLI login begins.
    - Remove superseded-token detection and revocation from the shared token
    persistence path; successful login now only saves the new credentials.
    - Read the raw configured auth store during CLI cleanup so
    environment-provided auth cannot mask the stored refresh token.
    - Preserve `auto` storage fallback semantics when keyring deletion fails
    by clearing the fallback auth file.
    - Add a process-level CLI regression test that requires the revoke
    request to precede every device-login request and occur exactly once.
    
    If replacement login is canceled or fails, the previous local
    credentials have already been cleared. Remote revocation remains best
    effort, matching explicit logout behavior.
    
    ## Validation
    
    ### Process-level before/after reproduction
    
    I compiled the real `codex` CLI from the pre-fix parent (`14df0e8833`)
    and from the PR implementation (`25c002f23b`; the login behavior is
    unchanged at the current head), then ran the same device-code flow
    against a local HTTP mock OAuth authority.
    
    Each run:
    
    1. Used a fresh temporary `CODEX_HOME` configured with
    `cli_auth_credentials_store = "file"`.
    2. Seeded that temporary home with managed ChatGPT auth containing
    `old-access` and `old-refresh` tokens.
    3. Pointed `CODEX_REVOKE_TOKEN_URL_OVERRIDE` at the mock `/oauth/revoke`
    endpoint.
    4. Ran the compiled CLI as:
    
       ```shell
       CODEX_HOME=<temporary-home> \
         CODEX_REVOKE_TOKEN_URL_OVERRIDE=<mock-issuer>/oauth/revoke \
    <compiled-codex> login --device-auth --experimental_issuer <mock-issuer>
       ```
    
    5. Recorded every request received by the mock authority. The mock
    marked `new-access` valid when `/oauth/token` issued it and invalidated
    it if `/oauth/revoke` arrived afterward, reproducing the observed
    session-invalidating failure mode. After login exited, the harness also
    verified the persisted refresh token and probed a protected endpoint
    with `new-access`.
    
    | Build | Observed request order | CLI/persistence result | `new-access`
    probe |
    | --- | --- | --- | --- |
    | Pre-fix | `usercode → device token → OAuth token →
    revoke(old-refresh)` | Exit `0`; `new-refresh` persisted | `401` |
    | PR | `revoke(old-refresh) → usercode → device token → OAuth token` |
    Exit `0`; `new-refresh` persisted | `200` |
    
    The PR run therefore issued exactly one revocation request, before any
    request that initiated the replacement login, and issued no revocation
    after token exchange.
    
    ### Regression coverage
    
    
    `codex-rs/cli/tests/login.rs::device_login_revokes_existing_auth_before_requesting_new_tokens`
    runs the real first-party `codex` binary against a `wiremock` OAuth
    server with an isolated temporary `CODEX_HOME`. It asserts:
    
    - the exact request sequence is `/oauth/revoke`,
    `/api/accounts/deviceauth/usercode`, `/api/accounts/deviceauth/token`,
    then `/oauth/token`;
    - there is exactly one revoke request and its body contains
    `old-refresh` with the `refresh_token` hint;
    - the completed login persists `new-refresh`.
    
    Local validation:
    
    - `just test -p codex-login` — 130 passed
    - `just test -p codex-cli` — 280 passed, including the new process-level
    regression test
    - `just bazel-lock-check`
  • sandboxing: migrate cwd inputs to PathUri (#27816)
    ## Why
    
    Sandbox cwd values can cross app-server and exec-server host boundaries.
    They should retain URI semantics until the receiving host validates them
    instead of being interpreted early as native paths.
    
    ## What
    
    - Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
    and transform inputs.
    - Convert command and policy cwd once in `SandboxManager::transform`,
    then keep launch requests native.
    - Preserve sandbox cwd over remote filesystem transport and reject
    non-native URIs without fallback.
    - Cache paired native/URI turn-environment cwd values during migration,
    with immutable access to keep them synchronized.
    - Extend existing protocol, forwarding, transform, and core runtime
    tests.
  • Extract shared plugin MCP config parsing (#27863)
    ## Why
    
    We want a thread-selected plugin to eventually expose stdio MCP servers
    that run on the executor owning that plugin.
    
    The existing plugin MCP parser lived inside `core-plugins` and was
    coupled to the host filesystem loader. Reusing it from an executor
    provider would either duplicate MCP normalization or make the plugin
    package layer own MCP runtime semantics. This PR creates the shared
    MCP-owned boundary first.
    
    In simple terms:
    
    ```text
    plugin .mcp.json
            |
            v
    shared parser in codex-mcp
            |
            +-- Declared placement: preserve current local-plugin behavior
            |
            +-- Environment placement: produce config bound to one executor
    ```
    
    This builds on the authority-bound plugin descriptors from #27692. It
    intentionally does not discover, register, or launch executor MCP
    servers yet.
    
    ## What changed
    
    - Moved plugin MCP file parsing and normalization from `core-plugins`
    into `codex-mcp`.
    - Kept support for both existing file shapes: a top-level server map and
    an object containing `mcpServers`.
    - Kept per-server failure isolation: one invalid server does not discard
    valid siblings, while malformed top-level JSON still fails the whole
    file.
    - Updated the existing local plugin loader to use `Declared` placement,
    preserving its current transport, OAuth, relative `cwd`, and error
    behavior.
    - Added `Environment` placement for the next stacked PR:
    - the selected environment ID overrides anything declared by the plugin;
      - missing stdio `cwd` defaults to the plugin root;
    - relative `cwd` is resolved beneath the plugin root and cannot traverse
    outside it;
    - bare or source-less environment-variable references resolve on a
    non-local executor;
    - explicit orchestrator environment-variable forwarding is rejected for
    executor-owned plugins.
    
    ## User impact
    
    None in this PR. Existing local plugin MCP loading follows the same
    behavior through the shared parser. The executor placement mode is not
    connected to thread startup until the follow-up registration PR.
    
    ## Assumptions
    
    - A selected capability root's environment is authoritative. A plugin
    cannot redirect its stdio process to the orchestrator or another
    executor.
    - Relative working directories belong under the plugin package root.
    Explicit absolute working directories remain valid within the owning
    environment.
    - For a non-local executor, unqualified environment-variable names refer
    to that executor. Reading an orchestrator variable requires an explicit
    contract and is rejected for now.
    - Parsing only produces normalized `McpServerConfig` values. Process
    startup remains owned by the existing MCP runtime and connection
    manager.
    
    ## Follow-ups
    
    1. Add the executor MCP provider and catalog registration: read the
    selected plugin's MCP config through the same executor filesystem,
    support stdio only, freeze the result per active thread, apply managed
    policy, and resolve name collisions as discovered plugin < selected
    plugin < explicit config.
    2. Install that provider in app-server and add an end-to-end test
    proving `thread/start.selectedCapabilityRoots` launches and calls the
    MCP tool on the selected executor, preserves the frozen registration
    across refresh, and does not expose it to an unselected thread.
    3. After the initial executor-stdio vertical, define
    resume/fork/environment-replacement semantics, executor HTTP placement,
    warning delivery, common MCP tool-context bounds, and move remaining MCP
    source composition above core.
    
    ## Verification
    
    - `cargo check -p codex-mcp -p codex-core-plugins --tests`
    - `just bazel-lock-check`
    - Added focused parser coverage for legacy local normalization, executor
    authority, working-directory handling, and environment-variable
    sourcing.
  • Add executor-owned plugin resolution (#27692)
    ## Why
    
    CCA can select a capability root that lives in an executor environment,
    but
    Codex only had a host-filesystem plugin loader. Before selected executor
    plugins can contribute MCP servers, we need a small package boundary
    that can
    answer:
    
    > Does this selected root contain a plugin, and if so, what does its
    manifest
    > declare?
    
    The answer must come from the selected environment's filesystem. A
    failed
    executor lookup must never fall back to the orchestrator filesystem.
    
    ## What this changes
    
    This PR introduces:
    
    ```rust
    PluginProvider::resolve(root)
        -> Result<Option<ResolvedPlugin>, Error>
    ```
    
    `ExecutorPluginProvider` resolves one `SelectedCapabilityRoot` through
    its
    exact `environment_id`. It checks the recognized manifest locations,
    reads the
    manifest through that environment's `ExecutorFileSystem`, and returns an
    inert
    `ResolvedPlugin` containing:
    
    - the opaque selected-root ID;
    - the environment-bound plugin root;
    - the authority-bound manifest resource;
    - parsed metadata and authority-bound component locators.
    
    Descriptor construction rejects manifest or component paths outside the
    selected package root, so consumers cannot accidentally lose the package
    boundary when they receive a resolved plugin.
    
    If the root has no plugin manifest, resolution returns `None`, allowing
    the
    caller to treat it as a standalone capability such as a skill.
    
    ```text
    selected root: repo -> env-1:/workspace/repo
                             |
                             | env-1 filesystem only
                             v
                 .codex-plugin/plugin.json
                             |
                             v
            ResolvedPlugin { authority, root, manifest }
    ```
    
    The existing host loader and the new executor provider now share the
    same
    manifest parser. Existing `codex-core-plugins::manifest` type paths
    remain
    available through re-exports, so host behavior and callers are
    unchanged.
    
    ## Scope
    
    This is intentionally a non-user-visible package-resolution PR. It does
    not:
    
    - parse or register plugin MCP server configurations;
    - activate skills, connectors, hooks, or MCP servers;
    - change app-server wiring;
    - introduce host fallback, caching, or lifecycle behavior.
    
    #27670 has merged, and this PR is now based directly on `main`. Together
    with
    the resolved MCP catalog from #27634, it establishes the inputs needed
    for the
    executor stdio MCP vertical without changing the existing MCP runtime.
    
    ## Follow-up
    
    The next PR will consume `ResolvedPlugin`, read its declared/default MCP
    config
    through the same executor filesystem, bind supported stdio servers to
    that
    environment, and feed those registrations into the resolved MCP catalog.
    An
    app-server E2E will prove that selecting an executor plugin exposes and
    invokes
    its tool on the owning executor.
    
    Resume/fork semantics, dynamic environment replacement, and non-stdio
    placement remain separate lifecycle decisions.
    
    ## Validation
    
    - `just fmt`
    - `cargo check --tests -p codex-plugin -p codex-core-plugins`
    - `just bazel-lock-check`
    - `git diff --check`
    
    Test targets were compiled but not executed locally; CI will run the
    test and
    Clippy suites.
  • code-mode standalone: extract protocol and add host crate (#27724)
    This is phase 1 of a 4 phase stack:
    1. **Add protocol and host crates for new IPC code mode implementation**
    2. Create the new standalone binary
    3. Create a new IPC `CodeModeSessionProvider` to use new binary
    4. Remove v8 from core and only use IPC provider
    
    
    ## Add protocol and host crates for new IPC code mode implementation
    Establish a clean process boundary without changing the existing
    in-process behavior.
    
    - Add the codex-code-mode-protocol crate for shared session, runtime,
    response, and tool-definition types.
    - Move protocol-facing code out of the V8-backed implementation.
    - Add a buildable codex-code-mode-host crate as the foundation for the
    standalone process.
    - Keep the existing in-process runtime as the active implementation.
  • [codex] Remove async_trait from first-party code (#27475)
    ## Why
    
    First-party async traits should expose their `Send` contracts explicitly
    without requiring `async_trait`. This completes the migration pattern
    established in #27303 and #27304.
    
    ## What changed
    
    - Replaced the remaining first-party `async_trait` traits with native
    return-position `impl Future + Send` where statically dispatched and
    explicit boxed `Send` futures where object safety is required.
    - Kept implementations behavior-preserving, outlining existing async
    bodies into inherent methods where that keeps the diff reviewable.
    - Removed all direct first-party `async-trait` dependencies and the
    workspace dependency declaration.
    - Added a cargo-deny policy that permits `async-trait` only through the
    remaining transitive wrapper crates.
    - Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
    keep the full cargo-deny check passing.
    
    ## Validation
    
    - `just test -p codex-exec-server`: 216 passed, 2 skipped.
    - `just test -p codex-model-provider`: 39 passed.
    - `just test -p codex-core` and `just test`: changed tests passed;
    remaining failures are environment-sensitive suites unrelated to this
    migration.
    - `cargo deny check`
    - `just fix`
    - `just fmt`
    - `cargo shear`
    - `just bazel-lock-check`
  • Fix image extension PathUri conversion (#27711)
    ## Why
    
    `main` stopped compiling when #27498 passed an `AbsolutePathBuf` to the
    `ExecutorFileSystem` API migrated to `PathUri` by #27653.
    
    ## What
    
    Convert referenced image paths to `PathUri` before filesystem reads,
    declare the internal path-URI dependency, and refresh `Cargo.lock`.
  • Route image extension reads through turn environments v2 (#27498)
    ## Why
    
    Image generation used `std::fs::read` for referenced image paths, which
    did not support environment-backed filesystems or their sandbox context.
    
    ## What changed
    
    - Expose optional turn environments to extension tool calls.
    - Include each environment’s ID, working directory, filesystem, and
    sandbox context.
    - Read referenced images through the selected environment filesystem.
    - Keep sandbox usage at the extension call site so extensions can choose
    the appropriate access mode.
    - Consolidate image request construction into one async function.
    - Add coverage for successful environment reads and read failures.
    
    ## Validation
    
    - `cargo check -p codex-image-generation-extension --tests`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    `just test -p codex-image-generation-extension` could not complete
    because the build exhausted available disk space.
  • [codex] Load user instructions through an injected provider (#27101)
    ## Why
    
    We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
    make embedders responsible for supplying user-level instructions. This
    also ensures user instructions load when no primary environment is
    selected.
    
    ## What changed
    
    Stacked on #27415, which makes `codex exec` surface thread-scoped
    runtime warnings.
    
    - Added `UserInstructionsProvider` to `codex-extension-api`, with
    absolute source attribution and recoverable loading warnings.
    - Added `codex-home` with the filesystem-backed provider for
    `AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
    trimming, lossy UTF-8 handling, and the existing uncapped global
    instruction size.
    - Removed global instruction loading from `Config` and require
    `ThreadManager` callers to inject a provider.
    - Load provider instructions once for each fresh root runtime, including
    runtimes without a primary environment. Running sessions retain their
    snapshot, while child agents inherit the parent snapshot without
    invoking the provider.
    - Keep provider instructions separate while loading project `AGENTS.md`,
    then assemble the model-visible instructions with the existing ordering,
    source attribution, warning, and turn-context behavior.
    - Wired the Codex home provider through the CLI, app server, MCP server,
    core facade, and thread-manager sample.
    
    ## Validation
    
    - `just test -p codex-home -p codex-extension-api`
    - `just test -p codex-core agents_md`
    - `just test -p codex-core guardian`
    - `just test -p codex-app-server
    thread_start_without_selected_environment_includes_only_global_instruction_source`
    - `just test -p codex-exec warning`
    - `just bazel-lock-check`
  • [codex] migrate ExecutorFileSystem paths to PathUri (#27424)
    ## Why
    
    We're moving exec-server to use PathUri for its internal path
    representations.
    
    ## What
    
    Move `ExecutorFileSystem` APIs to use `PathUri` instead of
    `AbsolutePathBuf`. Future changes will convert higher-level parts of
    exec-server.
  • skills: decouple the skills extension from core (#27413)
    ## Why
    
    `ext/skills` currently depends on `codex-core` for two host concerns:
    reading the concrete `Config` type and borrowing core-owned
    model-context fragment types. That coupling prevents the extension from
    being assembled independently above core and leaves context that belongs
    to the skills feature owned by core.
    
    This stacked PR introduces the host boundary needed for the broader
    extension migration while intentionally preserving existing skills
    behavior. It is stacked on #27404.
    
    ## What changed
    
    - Adds a small public `SkillsExtensionConfig` view and makes skills
    installation generic over the host config type.
    - Requires the host to map its config into that view; app-server
    supplies the current `Config` values.
    - Moves the available-skills and selected-skill context fragment
    implementations into `ext/skills`, preserving their roles, markers, and
    rendered bytes.
    - Removes the direct `codex-core` dependency from
    `codex-skills-extension`.
    - Keeps local discovery, invocation, side effects, and the
    `codex-core-skills` compatibility types unchanged for later staged PRs.
    
    ## Behavior
    
    This adds no capability and is intended to have no user-visible or
    model-visible behavior change. The install API and ownership boundary
    change internally; emitted skills context remains byte-for-byte
    compatible.
    
    ## Validation
    
    - Updates the skills extension integration coverage to use a host-owned
    test config.
    - Asserts the complete rendered catalog and selected-skill fragments,
    including their roles and markers.
    - `just bazel-lock-check`
    - Rust tests and Clippy were not run locally per request; CI will run
    them.
  • nit: cap error (#27585)
    Just cap an error that could end up in the model context
  • skills: expose remote skill resource tools (#27388)
    ## Why
    
    PR #27387 makes backend plugin skills discoverable and invocable without
    an executor, but resources referenced by those skills still sit behind
    the generic MCP resource surface. The model needs a skills-owned API
    that preserves the provider authority and package boundary instead of
    treating remote resources like local files.
    
    This is stacked on #27387.
    
    ## What
    
    - Adds one `skills` namespace with bounded `list` and `read` tools for
    remote skill providers.
    - Revalidates `authority + package` against the live remote catalog on
    every read, then routes the opaque resource ID back through that
    provider.
    - Allows the backend provider to read canonical child `skill://`
    resources while rejecting cross-package, non-canonical, query, fragment,
    and traversal-shaped URIs.
    - Caps each serialized tool result at 8 KB. Lists are paginated; reads
    return an opaque continuation cursor.
    - Marks the JSON output as external context so memory generation can
    apply its normal suppression policy.
    - Deliberately does not add `skills.search`; that waits for a bounded
    plugin-service search contract.
    
    ## Tool contract
    
    Pseudo-Python matching the wire shape:
    
    ```python
    from typing import Literal, NotRequired, TypedDict
    
    
    class RemoteSkillAuthority(TypedDict):
        kind: Literal["remote"]
        id: str  # e.g. "codex_apps"
    
    
    class RemoteSkill(TypedDict):
        authority: RemoteSkillAuthority
        package: str  # opaque provider-owned package ID
        name: str
        description: str
        main_resource: str  # opaque provider-owned SKILL.md ID
    
    
    class SkillsListParams(TypedDict):
        cursor: NotRequired[str]
    
    
    class SkillsListResult(TypedDict):
        skills: list[RemoteSkill]
        next_cursor: str | None
        warnings: list[str]
        truncated: bool
    
    
    class SkillsReadParams(TypedDict):
        authority: RemoteSkillAuthority  # copied from skills.list
        package: str  # copied from skills.list
        resource: str  # provider-owned child resource ID
        cursor: NotRequired[str]  # copy next_cursor to continue
    
    
    class SkillsReadResult(TypedDict):
        resource: str
        contents: str
        next_cursor: str | None
        truncated: bool
    
    
    class Skills:
        def list(self, params: SkillsListParams) -> SkillsListResult: ...
        def read(self, params: SkillsReadParams) -> SkillsReadResult: ...
    ```
    
    There is one namespace for all remote skills, not one tool or MCP server
    per skill. No resource ID is converted into a filesystem path.
    
    ## Backend dependency
    
    `/ps/mcp` must support direct reads of child resources such as
    `skill://plugin_demo/deploy/references/deploy.md`. This PR implements
    and tests the Codex side of that contract; production child reads remain
    dependent on the corresponding plugin-service support. Search remains
    out of scope until that service exposes a bounded search/resource API.
    
    ## Validation
    
    - Added an app-server integration test covering `skills.list` followed
    by `skills.read` with no executor.
    - Ran `just fmt`.
    - Ran `just bazel-lock-update` and `just bazel-lock-check`.
    - Did not run Rust tests or Clippy locally, per request; CI will run
    them.
  • skills: make backend plugin skills invocable without an executor (#27387)
    ## Why
    
    #27198 made the extension-owned `codex_apps` MCP connection the hosted
    plugin runtime, but its `mcp/skill` resources still bypassed the skills
    extension. App-server could list and read those resources through
    generic MCP APIs, but a thread with no selected environment did not
    expose them in the model's skills catalog or load their `SKILL.md`
    through `$skill`.
    
    Hosted skills should stay remote while using the same typed catalog,
    source authority, deduplication, bounded contextual catalog, and
    selected-skill prompt injection as host and executor skills. They should
    not be downloaded or exposed as ambient filesystem paths.
    
    ## What changed
    
    - Add a session-scoped `McpResourceClient` over the replaceable MCP
    connection manager so resource list/read calls follow startup and
    refresh replacements.
    - Add a `BackendSkillProvider` that pages `codex_apps` resources,
    accepts bounded and validated `mcp/skill` entries, and reads a selected
    skill's `SKILL.md` through the same MCP connection.
    - Register the remote provider in app-server and include it in the
    skills catalog even when a thread has no selected capability roots or
    executor.
    - Contribute hosted skill metadata through the bounded
    `AvailableSkillsInstructions` developer-context path, exclude remote
    entries from per-turn catalog injection, and classify `<skills>`
    messages as contextual developer content so rollback can trim and
    rebuild them correctly.
    
    ## Testing
    
    - Extend the app-server MCP resource integration test with
    `environments: []` to exercise two-page discovery, filter a
    non-`mcp/skill` resource, verify the escaped developer catalog entry and
    user-role `<skill>` fragment containing the fetched `SKILL.md`, and
    preserve generic MCP resource reads.
    - Add core event-mapping coverage that classifies `<skills>` developer
    messages as contextual history.
  • [codex] 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.
  • 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-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`
  • 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
  • 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] 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.
  • 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.
  • Route image edits through referenced file paths (#26486)
    ## Why
    
    Image edits should use the exact images selected by the model instead of
    inferring edit inputs from conversation history.
    
    ## What changed
    
    - Replaced the image tool's `action` argument with optional
    `referenced_image_paths`.
    - Treats omitted or empty references as generation and populated
    references as editing.
    - Reads referenced absolute image paths and packages them as image data
    URLs for the edit request.
    - Removed the previous history-selection and image-count heuristics.
    - Updated direct and code-mode tool instructions and calls.
    - Added an app-server integration test covering an attached image routed
    to the image edit endpoint.
    
    ## Validation
    - Tested end-to-end on local `just codex` with copy pasted image,
    attached image, etc.
    - `just test -p codex-image-generation-extension`
    - `just test -p codex-app-server
    standalone_image_edit_uses_attached_model_visible_image`
    - `just fix -p codex-image-generation-extension`
    - `just bazel-lock-check`
  • [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.
  • [codex] Add environment shell info (#26480)
    ## Why
    
    Shell detection needs to be available through the `Environment`
    abstraction so callers can ask the selected local or remote environment
    for shell metadata without adding a separate HTTP endpoint or parallel
    info-source path. This keeps shell metadata shaped like the existing
    environment-owned filesystem capability and lets remote environments
    answer through exec-server JSON-RPC.
    
    ## What changed
    
    - Added `environment/info` to the exec-server protocol/client/server and
    exposed `Environment::info()`.
    - Added local and remote environment info providers on `Environment`,
    following the existing capability-provider pattern used for filesystem
    access.
    - Moved the shared shell detection logic into `codex-shell-command` and
    kept core shell APIs as wrappers around that implementation.
    - Returned shell metadata as `EnvironmentInfo { shell: ShellInfo }`
    using the existing shell detection path.
    - Added a remote environment test that calls `Environment::info()`
    through an exec-server-backed environment.
    
    ## Validation
    
    - `git diff --check`
    - `just test -p codex-shell-command`
    - `just test -p codex-core -E 'test(/shell::tests::/)'`\n- `just test -p
    codex-exec-server environment`
  • fix(rmcp): refresh expired OAuth tokens before startup (#26482)
    ## Why
    
    Codex persists OAuth expiry as an absolute `expires_at`, then
    reconstructs RMCP’s relative `expires_in` when credentials are loaded.
    For an already-expired token, Codex reconstructed `expires_in` as
    missing.
    
    [RMCP 0.15 treated a missing `expires_in` as zero when a refresh token
    was
    present](https://github.com/modelcontextprotocol/rust-sdk/blob/9cfc905a9ef17c8bba6748dc0a9bdd2452681733/crates/rmcp/src/transport/auth.rs#L704-L723),
    so this still triggered a refresh. [RMCP 1.7 treats missing expiry
    information as unknown and uses the access token
    as-is](https://github.com/modelcontextprotocol/rust-sdk/blob/3529c3675ff64db805bd947ca6ece6090809e43d/crates/rmcp/src/transport/auth.rs#L1233-L1265),
    causing the stale token to be sent during `initialize`.
    
    ## What changed
    
    - Represent a known-expired persisted token as `expires_in = 0`,
    preserving `None` for genuinely unknown expiry.
    - Add Streamable HTTP coverage requiring the token to refresh before the
    startup handshake.
    
    ## Validation
    
    - The new regression test fails on RMCP 1.7 before the fix and passes
    afterward.
    - The same scenario passes on the commit immediately before the RMCP 1.7
    update, using RMCP 0.15.
    - `just test -p codex-rmcp-client` (63 passed).
  • Add saved image path hint to standalone image generation (#25947)
    ## Why
    
    Standalone image generation returns image bytes to the model, but the
    model also needs the host artifact path to reference the generated file
    in follow-up work.
    
    ## What changed
    
    - Append the default saved-image path hint alongside the generated image
    tool output.
    - Reuse the existing core image-generation hint text.
    - Pass the thread ID and Codex home directory needed to compute the
    artifact path.
    - Add app-server and extension coverage for the model-visible hint.
    
    ## Validation
    
    - `just fmt`
    - `just bazel-lock-check`
    - `just test -p codex-app-server
    standalone_image_generation_returns_saved_path_hint_to_model`
  • Optimize unbounded byte scans with memchr (#26265)
    ## Summary
    
    This PR adds `memchr` for some low-hanging performance improvements
    (namely, in MCP stdio, Ollama streaming, and full message-history
    newline counts).
    
    Codex produced the following release benchmarks:
    
    | Operation | Before | After | Speedup |
    | --- | ---: | ---: | ---: |
    | MCP 1 MiB chunked line | 2.172 s | 3.984 ms | 545x |
    | Ollama 1 MiB chunked line | 1.673 s | 2.790 ms | 600x |
    | Count newlines in 10 MiB history | 132.83 ms | 20.05 ms | 6.6x |
    
    With a "real" MCP setup (`ExecutorStdioServerLauncher` started a Python
    MCP server, completed `initialize`, requested `tools/list`, and
    deserialized a 1 MiB tool description over newline-delimited stdio),
    it's about 16x faster end-to-end:
    
    | Branch | 50 calls | Per call |
    | --- | ---: | ---: |
    | `main` | 862.53 ms | 17.25 ms |
    | this branch | 53.89 ms | 1.08 ms |
    
    `memchr` is already in our dependency tree and extremely widely used for
    this kind of optimized scanning.
  • cli: add package path from install context (#26189)
    ## Why
    
    Codex package installs include helper binaries in `codex-path`, such as
    the bundled `rg`. Package-layout launches should add that directory
    before user commands run, but standalone launches were missing it while
    npm launches only worked because `codex.js` had its own legacy `PATH`
    rewrite. That made npm and standalone package behavior diverge.
    
    Shell snapshot restoration can also reset `PATH` after runtime setup.
    Any package-owned `PATH` prepend has to be recorded as an explicit
    runtime override so shells, unified exec, and user-shell commands keep
    access to `codex-path` after a snapshot is sourced.
    
    ## Repro
    
    Before this change, a curl-installed package could contain `rg` under
    `codex-path` but still fail to put it on `PATH`:
    
    ```shell
    mkdir /tmp/test-codex-curl
    curl -fsSL https://chatgpt.com/codex/install.sh \
      | CODEX_HOME=/tmp/test-codex-curl CODEX_NON_INTERACTIVE=1 sh
    /tmp/test-codex-curl/packages/standalone/current/bin/codex exec \
      --skip-git-repo-check 'print `which -a rg`'
    find /tmp/test-codex-curl -name rg
    ```
    
    The `which -a rg` output omitted the packaged helper even though `find`
    showed it under
    `/tmp/test-codex-curl/packages/standalone/releases/.../codex-path/rg`.
    
    The npm install path behaved differently only because
    `codex-cli/bin/codex.js` had legacy `PATH` rewriting:
    
    ```shell
    mkdir /tmp/test-codex-npm
    cd /tmp/test-codex-npm
    npm install @openai/codex
    ./node_modules/.bin/codex exec --skip-git-repo-check 'print `which -a rg`'
    ```
    
    That printed the npm package's `vendor/<target>/codex-path/rg` first.
    This PR moves that behavior into Rust-side package launch setup so
    curl/standalone and npm/bun launches agree without JS rewriting `PATH`.
    
    ## What Changed
    
    - `codex-rs/arg0` now uses
    `InstallContext::current().package_layout.path_dir` to prepend the
    package helper directory before any threads are created.
    - Package helper `PATH` setup is independent from the temporary arg0
    alias setup, so `codex-path` is still added even if CODEX_HOME tempdir,
    lock, or symlink setup fails.
    - `codex-rs/install-context` detects the canonical package layout we
    ship: `bin/`, `codex-resources/`, and `codex-path/` next to
    `codex-package.json`.
    - Shell, local unified exec, and user-shell runtimes now record package
    `codex-path` prepends in `explicit_env_overrides`, matching the existing
    zsh-fork behavior so shell snapshots cannot restore over the package
    helper path.
    - Remote unified exec requests do not receive the local app-server
    package path overlay.
    - `codex-cli/bin/codex.js` no longer computes or overrides `PATH`; it
    only locates the native binary in the canonical package layout and
    passes npm/bun management metadata.
    - Added regression tests for `PATH` ordering, package layout detection,
    and shell snapshot preservation of package path prepends.
    
    ## Verification
    
    - `node --check codex-cli/bin/codex.js`
    - `just test -p codex-install-context -p codex-arg0`
    - `just test -p codex-core
    user_shell_snapshot_preserves_package_path_prepend`
    - `just test -p codex-core tools::runtimes::tests`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-install-context -p codex-arg0 -p codex-core`
  • Implement v1 skills extension prompt injection (#26167)
    ## Why
    
    The skills extension needs a real turn-time path before host, executor,
    or remote skills can be routed through it. The previous code was mostly
    a placeholder catalog/provider sketch, so there was no bounded
    available-skills fragment, no source-owned `SKILL.md` read, and no place
    for warnings or per-turn selection state to live.
    
    This PR makes `ext/skills` the authority-preserving flow for listing
    candidate skills and injecting only explicitly selected main prompts,
    without adding more of that logic to `codex-core`.
    
    ## What changed
    
    - Expands catalog entries with `main_prompt`, display path, short
    description, dependency metadata, enabled/prompt visibility flags, and
    authority/package-aware read requests.
    - Replaces the placeholder `providers/*` modules with
    `SkillProviderSource` and `SkillProviders`, routing list/read/search
    calls by source kind and surfacing provider failures as warnings.
    - Adds bounded available-skills rendering and `SKILL.md` main-prompt
    truncation before the fragments enter model context.
    - Resolves explicit skill selections from structured `UserInput::Skill`,
    skill-file mentions, `skill://...` paths, and plain `$skill` text
    mentions, then reads selected prompts through their owning provider.
    - Stores mutable per-thread skills config and per-turn
    catalog/selection/warning state.
    - Adds `install_with_providers` so tests and future host wiring can
    supply concrete providers.
    
    ## Testing
    
    - Not run locally.
    - Added `codex-rs/ext/skills/tests/skills_extension.rs` coverage for
    available-catalog injection, selected prompt injection through the
    owning provider, and prompt-hidden skills that remain invokable.
  • skills: resolve per-turn catalogs from turn input context (#26106)
    ## Why
    
    The skills extension needs the resolved turn environments to build a
    real per-turn `SkillListQuery`. The previous `TurnLifecycleContributor`
    hook only had a turn id, so it could only seed a placeholder query and
    never carry the executor authorities that executor-scoped skill routing
    will need.
    
    Moving catalog resolution onto `TurnInputContributor` puts the skills
    extension on the same turn-preparation path that already has the
    environment ids and working directories for the submitted turn, while
    keeping the actual prompt injection work for follow-up changes.
    
    ## What changed
    
    - switch `ext/skills` from `TurnLifecycleContributor` to
    `TurnInputContributor`
    - build `executor_authorities` from `TurnInputContext.environments` and
    pass them through `SkillListQuery`
    - keep storing the resolved catalog in `SkillsTurnState`, but drop the
    placeholder query helper that no longer matches the real data flow
    - update the extension TODOs to reflect that per-turn catalog resolution
    now happens in the turn-input contributor, and that prompt/context
    injection still needs to move later
    
    ## Testing
    
    - Not run locally.
  • chore: extract context fragments into dedicated crate (#26122)
    ## Why
    
    `codex-core` currently owns the generic contextual-fragment trait and
    several reusable fragment implementations. That makes it harder for
    other crates to share the same host-owned model-input abstraction
    without depending on all of `codex-core`.
    
    This change extracts the reusable fragment machinery into a small
    `codex-context-fragments` crate so future extension and skills work can
    depend on the fragment abstraction directly.
    
    ## What Changed
    
    - Added the `codex-context-fragments` crate with:
      - `ContextualUserFragment`
      - `FragmentRegistration` / `FragmentRegistrationProxy`
      - additional-context fragment types
    - Moved `SkillInstructions` into `codex-core-skills`, since
    skill-specific rendering belongs with skills rather than generic core
    context machinery.
    - Kept `codex-core` re-exporting the fragment types it still uses
    internally, so existing call sites keep the same shape.
    - Updated Cargo and Bazel workspace metadata for the new crate.
    
    ## Verification
    
    - `cargo metadata --locked --format-version 1 --no-deps`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
  • feat: add skills extension scaffold (#25953)
    ## Disclaimer
    This is only here for iteration purpose! Do not make any code rely on
    this
    
    ## Why
    
    Skills still live behind `codex-core` discovery and injection paths, but
    the extension system needs an authority-aware home before that logic can
    move. This adds that boundary without changing current skills behavior,
    and keeps host, executor, and remote skills distinct so future
    list/read/search flows do not collapse back to ambient local paths.
    
    ## What changed
    
    - Add the `codex-skills-extension` workspace/Bazel crate under
    `ext/skills`.
    - Define the initial catalog, authority, provider, and turn-state types
    for authority-bound skill packages and resources.
    - Register placeholder thread/config/prompt/turn lifecycle contributors
    plus host, executor, and remote provider aggregation points.
    - Capture the remaining extraction work as TODOs, including the missing
    extension API hooks needed for per-turn catalog construction and typed
    skill injection.
    - Keep plugins outside the runtime skills model: plugin-installed skills
    are treated as materialized host-owned skill sources once available.
    
    ## Verification
    
    - Not run locally.
  • Split cloud config bundle service modules (#25668)
    ## Summary
    
    - Splits the monolithic `codex-cloud-config` implementation into focused
    modules.
    - Keeps behavior unchanged from the preceding config bundle runtime
    switch.
    
    ## Details
    
    This is the reviewability follow-up after the lineage-preserving
    migration PRs. The split separates backend transport, loader
    construction, cache handling, metrics, validation, service
    orchestration, and focused tests into named files.
    
    Verification: `just fmt`; `just test -p codex-cloud-config`.
  • Switch runtime to cloud config bundle (#24622)
    ## Summary
    
    - Adapts the moved `codex-cloud-config` crate from the legacy cloud
    requirements endpoint to the new config bundle endpoint.
    - Switches runtime consumers from `CloudRequirementsLoader` to
    `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
    config and requirements.
    - Removes the legacy cloud requirements domain loader path.
    
    ## Details
    
    This intentionally keeps `codex-cloud-config` monolithic for review
    lineage: the previous PR establishes the crate move, and this PR shows
    the behavior change against that moved implementation. A follow-up PR
    splits the module back into focused files.
    
    The new bundle path preserves the important cloud requirements loader
    semantics where intended: account-scoped signed cache, 30 minute TTL, 5
    minute refresh cadence, retry/backoff, auth recovery, and fail-closed
    startup loading. The cached payload changes from a single requirements
    TOML string to the backend-delivered bundle, and validation rejects
    malformed config or requirements fragments before cache write/use.
  • Move cloud requirements crate to cloud config (#24621)
    ## Summary
    
    - Moves the existing `codex-cloud-requirements` crate to
    `codex-cloud-config`.
    - Updates workspace dependencies and imports to the new crate name.
    - Intentionally keeps runtime behavior unchanged: this still fetches the
    legacy cloud requirements endpoint.
    
    ## Details
    
    This PR exists to make the lineage obvious before the bundle migration.
    GitHub should show the old `codex-rs/cloud-requirements/src/lib.rs`
    implementation as moved to `codex-rs/cloud-config/src/lib.rs`, rather
    than as unrelated new code.
    
    The follow-up PR adapts this moved crate to the new config bundle API
    and switches runtime consumers over.
  • app-server: remove experimental persist_extended_history bool flag (#25712)
    ## Summary
    
    Remove the dead experimental `persistExtendedHistory` app-server flag
    and collapse rollout persistence to the single policy app-server already
    used.
    
    ## What Changed
    
    - Removed `persistExtendedHistory` from v2 thread start/resume/fork
    params and deleted its deprecation notice path.
    - Removed the persistence-mode enums and plumbing through core, rollout,
    and thread-store.
    - Made rollout filtering mode-free, keeping the existing limited
    persisted-history behavior.
    
    ## Test Plan
    
    - `just write-app-server-schema`
    - `cargo nextest run --no-fail-fast -p codex-app-server-protocol
    schema_fixtures`
    - `cargo nextest run --no-fail-fast -p codex-app-server
    thread_shell_command_history_responses_exclude_persisted_command_executions`
    - `cargo nextest run --no-fail-fast -p codex-rollout -p
    codex-thread-store`
    - final `rg` for removed flag/type names
  • Wire managed MITM CA trust into child env (#22668)
    ## Stack
    1. Parent PR: #18240 uses named MITM permissions config.
    2. This PR wires managed MITM CA trust into spawned child processes.
    
    ## Why
    When Codex terminates HTTPS for limited mode or MITM hooks, child HTTPS
    clients need to trust Codex's managed MITM CA. Exporting proxy URLs
    alone is not enough, but blindly replacing user CA settings would be
    wrong: it can break custom enterprise/test roots, leak unreadable CA
    files into generated bundles, or make the child env disagree with its
    sandbox policy.
    
    ## Summary
    1. Build immutable managed CA bundles under `$CODEX_HOME/proxy` that
    include native roots, the managed MITM CA, and only inherited or
    command-scoped CA bundles the child is allowed to read.
    2. Export curated CA env vars alongside managed proxy env vars while
    preserving user CA override semantics, including nested Codex
    `SSL_CERT_FILE` precedence.
    3. Thread generated CA bundle paths into child sandbox readable roots,
    including debug sandbox execution, so the exported env vars work inside
    sandboxed commands.
    4. Remove only Codex-generated MITM CA bundle env when a child
    intentionally drops managed proxying for escalation or no-proxy retry.
    5. Document the managed CA bundle behavior and cover env injection,
    per-child bundle generation, sandbox readable roots, and no-proxy
    cleanup in tests.
    
    ## Validation
    1. Ran `just test -p codex-network-proxy`.
    2. Ran `just test -p codex-protocol`.
    3. Ran `just fix -p codex-network-proxy -p codex-protocol`.
    4. Tried focused `codex-core` validation, but the crate currently fails
    to compile in `core/tests/suite/guardian_review.rs` because an existing
    `Op::UserInput` initializer is missing `additional_context`.
    
    ---------
    
    Co-authored-by: Eva Wong <evawong@openai.com>
  • [codex] Consolidate shared prompts in codex-prompts (#25151)
    ## Why
    
    `codex_core` is consistently a bottleneck for incremental builds during
    iteration. The simplest fix is to make the crate smaller.
    
    ## Summary
    
    `codex-core` owns several reusable prompt renderers and static prompt
    assets, which makes the crate harder to split apart.
    
    Rename `codex-review-prompts` to `codex-prompts` and move shared review,
    goal, permissions, compaction, realtime, hierarchical AGENTS.md, and
    `apply_patch` prompts into it. Move prompt-only tests and update
    consumers and `CODEOWNERS`.
    
    ## Validation
    
    - `just test -p codex-prompts -p codex-apply-patch`
    - `just test -p codex-core prompt_caching`
    - Bazel builds for the affected crates
  • Preserve plugin app manifest order (#25491)
    ## Summary
    - Preserve app declaration order when loading plugin .app.json files.
    - Keep plugin connector summaries in plugin app order after connector
    metadata is merged and filtered.
    - Add regression coverage for .app.json order and connector summary
    order.
    
    ## Validation
    - just fmt
    - just test -p codex-chatgpt
    connectors_for_plugin_apps_returns_only_requested_plugin_apps
    - just test -p codex-core-plugins
    effective_apps_preserves_app_config_order
    - just fix -p codex-core-plugins (passes with existing clippy
    large_enum_variant warning in core-plugins/src/manifest.rs)
    - just fix -p codex-chatgpt
    - just bazel-lock-update
    - just bazel-lock-check
  • Read compressed rollouts and materialize before append (#25087)
    ## Why
    
    Local rollout compression needs a cold `.jsonl.zst` representation
    without letting compressed physical paths leak into append-mode writers.
    The unsafe case is resume or metadata update code successfully reading a
    compressed rollout and then appending raw JSONL bytes to the zstd file.
    
    This PR folds the former #25088 materialization slice into the
    read-support PR so the reader changes and append-safety invariant land
    together.
    
    ## What Changed
    
    - Teach rollout readers, discovery, listing, search, and ID lookup to
    understand compressed `.jsonl.zst` rollouts.
    - Keep `.jsonl` as the logical/stored rollout path while allowing read
    paths to open either plain or compressed storage.
    - Materialize compressed rollouts back to plain `.jsonl` before
    append-mode writes, including resume and direct metadata append paths.
    - Preserve compressed-file permissions when materializing back to plain
    JSONL.
    - Refresh thread-store resolved rollout paths after compatibility
    metadata writes so reconciliation follows the materialized file.
    - Avoid treating transient compression temp files as real rollout lookup
    results.
    
    ## Remaining Stack
    
    #25089 remains the separate worker PR. It is based directly on this PR
    and stays behind the disabled `local_thread_store_compression` feature
    flag.
    
    The worker still has a broader coordination question: a resume or
    metadata update can race with background compression while a plain file
    is being replaced by `.jsonl.zst`. This PR handles the read and
    materialize-before-append primitives; it does not make the worker
    production-ready.
    
    ## Validation
    
    - `just test -p codex-rollout`
    - `just test -p codex-thread-store`
    - `just fix -p codex-rollout`
    - `just fix -p codex-thread-store`
    - `just bazel-lock-check`
  • Use templates for goal steering prompts (#25576)
    ## Why
    
    Goal steering prompts have grown into long inline Rust strings, which
    makes the authored prompt text hard to review and easy to damage while
    changing the surrounding plumbing. Moving those prompts into embedded
    Markdown templates keeps the policy text in the shape reviewers actually
    read, while preserving the existing runtime substitution and objective
    escaping behavior.
    
    ## What changed
    
    - Added `ext/goal/templates/goals/continuation.md`, `budget_limit.md`,
    and `objective_updated.md` for the three goal steering prompts.
    - Updated `ext/goal/src/steering.rs` to parse those embedded templates
    once with `codex-utils-template` and render the existing goal values
    into them.
    - Kept user objectives XML-escaped before rendering and converted budget
    counters into template variables.
    - Added the template directory to `ext/goal/BUILD.bazel` `compile_data`
    so Bazel has the same embedded prompt inputs as Cargo.
    
    ## Testing
    
    - Not run locally.
  • code-mode: introduce durable session interface (#24180)
    ## Summary
    
    Introduce a `CodeModeSession` interface for executing and managing
    code-mode cells.
    
    This moves cell lifecycle, callback delegation, termination, and
    shutdown behind a session abstraction, while continuing to use the
    existing in-process implementation, and the ability to implement an
    external process one behind this interface.
    
    A Codex session owns one `CodeModeSession`, which in turn owns its
    running cells and stored code-mode state. Each cell is represented to
    the caller as a `StartedCell`, exposing its cell ID and initial
    response.
    
    It also introduces a `CodeModeSessionDelegate` callback interface. A
    session uses the delegate to invoke nested host tools and emit
    notifications while a cell is running, allowing the runtime to
    communicate with its owning Codex session without depending directly on
    core turn handling.
    
    <img width="2121" height="1001" alt="image"
    src="https://github.com/user-attachments/assets/c349a819-2a59-485c-bda4-2caf68ac4c31"
    />