Commit Graph

1061 Commits

  • fix(app-server): thread history redaction for remote clients (#22178)
    ## Summary
    
    Remote clients can still receive large `thread/resume` histories when
    prior turns include MCP tool call payloads or image-generation results.
    This adds a temporary response-only redaction path for the known remote
    client names.
    
    Longer term we will move towards fully paginated APIs backed by SQLite.
    
    ## Changes
    
    - Redact MCP tool call payload-bearing fields in `thread/resume`
    responses for `codex_chatgpt_android_remote` and
    `codex_chatgpt_ios_remote`.
    - Drop `imageGeneration` items from those `thread/resume` responses.
    - Keep redaction out of persisted rollout files, `thread/read`,
    `thread/turns/list`, live notifications, and token usage replay.
    - Cover the behavior with app-server helper tests and a v2 resume
    integration test that checks both remote clients plus a non-target
    control client.
    
    ## Testing
    
    - `cargo test -p codex-app-server thread_resume_redaction`
    - `cargo test -p codex-app-server
    thread_resume_redacts_payloads_for_chatgpt_remote_clients`
  • Unified mentions in TUI (#19068)
    This PR replaces the TUI’s file-only `@mention` popup with a unified
    mentions experience. Typing `@...` now searches across filesystem
    matches, installed plugins, and skills in one popup, with result types
    clearly labeled and selectable from the same flow.
    
    - Adds a unified `@mentions` popup that returns:
      - plugins
      - skills
      - files
      - directories
    
    - Adds search modes so users can narrow the popup without changing their
    query:
      - All Results _(default/same as Codex App)_
      - Filesystem Only
      - Plugins _(...and skills)_
    
    - Preserves existing insertion behavior:
      - selected file paths are inserted into the prompt
      - paths with spaces are quoted
      - image file selections still attach as images when possible
      - selecting a plugin or skill inserts the corresponding `$name`
    - the composer records the canonical mention binding, such as
    `plugin://...` or the skill path
    
    - Expanded `@mentions` rendering:
      - type tags for Plugin, Skill, File, and Dir
      - distinct plugin/filesystem colors
      - stable fixed-height layout (8 rows)
      - truncation behavior for narrow terminals
    
    Note:
    - The unified mentions popup does not display app connectors under
    `@mention` results for Codex App parity. Connector mentions remain
    available through the existing `$mention` path.
    
    
    https://github.com/user-attachments/assets/f93781ed-57d3-4cb5-9972-675bc5f3ef3f
  • Add process-scoped SQLite telemetry (#22154)
    ## Summary
    - add SQLite init, backfill-gate, and fallback telemetry without
    introducing a cross-cutting state-db access wrapper
    - install one process-scoped telemetry sink after OTEL startup and let
    low-level state/rollout paths emit through it directly
    - add process-start metrics for the process owners that initialize
    SQLite
    
    ---------
    
    Co-authored-by: Owen Lin <owen@openai.com>
  • Fix goal update and add /goal edit command in TUI (#21954)
    ## Why
    
    Users have requested the ability to edit a goal's objective after a goal
    has been created. This PR exposes a new `/goal edit` command in the TUI
    to address this request.
    
    In the process of implementing this, I also noticed an existing bug in
    the goal runtime. When a goal's objective is updated through the
    `thread/goal/set` app server API, the goal runtime didn't emit a new
    steering prompt to tell the agent about the new objective. This PR also
    fixes this hole.
    
    ## What Changed
    
    - Adds `/goal edit` in the TUI, opening an edit box prefilled with the
    current goal objective.
    - Keeps active and paused goals in their current state, resets completed
    goals to active, keeps budget-limited goals budget-limited, and
    preserves the existing token budget.
    - Changes the existing `thread/goal/set` behavior so editing an
    objective preserves goal accounting instead of resetting it. The older
    reset-on-new-objective behavior was left over from before
    `thread/goal/clear`; clients that need to reset accounting can now clear
    the existing goal and create a new one.
    - Reuses the existing goal set API path; this does not add or change
    app-server protocol surface area.
    - Adds a dedicated goal runtime steering prompt when an externally
    persisted goal mutation changes the objective, so active turns receive
    the updated objective.
    
    ## Validation
    
    - Make sure `/goal edit` returns an error if no goal currently exists
    - Make sure `/goal edit` displays an edit box that can be optionally
    canceled with no side effects
    - Make sure that an edited goal results in a steer so the agent starts
    pursuing the new objective
    - Make sure the new objective is reflected in the goal if you use
    `/goal` to display the goal summary
    - Make sure that `/goal edit` doesn't reset the token budget, time/token
    accounting on the updated goal
  • chore: drop built-in MCPs (#22173)
    Drop something that was never used
  • app-server: remove TCP websocket listener (#21843)
    ## Why
    
    The app-server no longer needs to expose a TCP websocket listener.
    Keeping that transport also kept around a separate listener/auth surface
    that is unnecessary now that local clients can use stdio or the
    Unix-domain control socket, while remote connectivity is handled by
    `remote_control`.
    
    ## What Changed
    
    - Removed `ws://IP:PORT` parsing and the `AppServerTransport::WebSocket`
    startup path.
    - Deleted the app-server websocket listener auth module and removed
    related CLI flags/dependencies.
    - Kept websocket framing only where it is still needed: over the
    Unix-domain control socket and in the outbound `remote_control`
    connection.
    - Updated app-server CLI/help text and `app-server/README.md` to
    document only `stdio://`, `unix://`, `unix://PATH`, and `off` for local
    transports.
    - Converted affected app-server integration coverage from TCP websocket
    listeners to UDS-backed websocket connections, and added a parse test
    that rejects `ws://` listen URLs.
    - Removed the now-unused workspace `constant_time_eq` dependency and
    refreshed `Cargo.lock` after `cargo shear` caught the drift.
    - Moved test app-server UDS socket paths to short Unix temp paths so
    macOS Bazel test sandboxes do not exceed Unix socket path limits.
    
    ## Verification
    
    - Added/updated tests around UDS websocket transport behavior and
    `ws://` listen URL rejection.
    - `cargo shear`
    - `cargo metadata --no-deps --format-version 1`
    - `cargo test -p codex-app-server unix_socket_transport`
    - `cargo test -p codex-app-server unix_socket_disconnect`
    - `just fix -p codex-app-server`
    - `git diff --check`
    
    Local full Rust test execution was blocked before compilation by an
    external fetch failure for the pinned `nornagon/crossterm` git
    dependency. `just bazel-lock-update` and `just bazel-lock-check` were
    retried after the manifest cleanup but remain blocked by external
    BuildBuddy/V8 fetch timeouts.
  • Use goal preview metadata for goal-first threads (#21981)
    Fixes #20792
    
    ## Why
    
    `/goal`-first threads are valid resumable threads, but they can be
    missing from `codex resume` and app recents because discovery depends on
    metadata derived from a normal first user message.
    
    PR #21489 attempted to fix this by using the goal objective as
    `first_user_message`. Review feedback pointed out that
    `first_user_message` does more than provide visible text today: it gates
    listing, supplies preview text, and participates in deciding whether a
    later title should surface as a distinct thread name. Reusing it for the
    goal objective could leave a `/goal`-first thread with
    `first_user_message=<goal>` and `title=<later prompt>`, even though the
    goal should only provide the initial visible preview.
    
    This PR follows that feedback by and keeps the `first_user_message` as
    is but introduces a new `preview` field to separate concerns. The
    `preview` field is populated from the first user message or the goal
    objective. We can extend it in the future to include other sources.
    
    ## What Changed
    
    - Added internal thread `preview` metadata in `codex-state`, including a
    SQLite migration that backfills from `first_user_message` and from
    existing `thread_goals` objectives when needed.
    - Treated `ThreadGoalUpdated` as preview-bearing metadata so goal-first
    threads can be listed and searched without mutating
    `first_user_message`.
    - Updated rollout listing, state queries, thread-store conversion, and
    app-server mapping to use preview metadata while continuing to expose
    the existing public `preview` field.
    - Preserved title/name distinctness behavior around literal
    `first_user_message`, so a later normal prompt after `/goal` does not
    surface as a separate name just because the goal supplied the initial
    preview.
    - Preserved compatibility for older/internal metadata writes by deriving
    preview from `first_user_message` when explicit preview metadata is
    absent.
    
    ## Verification
    
    - Manually verified that a thread that starts with a `/goal <objective>`
    shows up in the resume picker.
  • feat: drop CodexExtension (#22140)
    Drop `CodexExtension` as not needed for now
  • extension: move git attribution into an extension (#21738)
    ## Why
    
    Git commit attribution is prompt policy, not session orchestration.
    After #21737 adds the extension-registry seam, this moves that
    prompt-only behavior out of `codex-core` so `Session` can consume
    extension-contributed prompt fragments instead of owning a one-off
    policy path itself.
    
    Before this PR, `Session` injected the trailer instruction directly from
    `codex-core` ([session
    assembly](https://github.com/openai/codex/blob/a57a747eb667753118217b8bb47dfd1fff88cbde/codex-rs/core/src/session/mod.rs#L2733-L2739),
    [helper
    module](https://github.com/openai/codex/blob/a57a747eb667753118217b8bb47dfd1fff88cbde/codex-rs/core/src/commit_attribution.rs#L1-L33)).
    This branch moves that same responsibility into
    [`codex-git-attribution`](https://github.com/openai/codex/blob/b5029a67360fe5c948aa849d4cf65fd2597ebaae/codex-rs/ext/git-attribution/src/lib.rs#L14-L100).
    
    ## What changed
    
    - Added the `codex-git-attribution` extension crate.
    - Snapshot `CodexGitCommit` plus `commit_attribution` at thread start,
    then contribute the developer-policy fragment through the extension
    registry.
    - Register the extension in app-server thread extensions.
    - Remove the old `codex-core` helper module and direct `Session`
    injection path.
    
    This keeps the existing behavior intact: the prompt is only contributed
    when `CodexGitCommit` is enabled, blank attribution still disables the
    trailer, and the default remains `Codex <noreply@openai.com>`.
    
    ## Stack
    
    - Stacked on #21737.
  • extension: wire extension registries into sessions (#21737)
    ## Why
    
    [#21736](https://github.com/openai/codex/pull/21736) introduces the
    typed extension API, but the runtime does not yet carry a registry
    through thread/session startup or give contributors host-owned stores to
    read from. This PR wires that host-side path so later feature migrations
    can move product-specific behavior behind typed contributions without
    adding another bespoke seam directly to `codex-core`.
    
    ## What changed
    
    - Thread `ExtensionRegistry<Config>` through `ThreadManager`,
    `CodexSpawnArgs`, `Session`, and sub-agent spawn paths.
    - Wire `ThreadStartContributor` and `ContextContributor`
    - Expose the small supporting surface needed by non-core callers that
    construct threads directly, including `empty_extension_registry()`
    through `codex-core-api`.
    
    This PR lands the host plumbing only: the app-server registry is still
    empty, and concrete feature migrations are intended to follow
    separately.
  • Read cached metadata for installed Git plugins (#20825)
    ## Summary
    - Populate `plugin/list` interface metadata for installed Git-sourced
    marketplace plugins from the active cached plugin bundle.
    - Preserve marketplace category precedence so list behavior matches
    `plugin/read`.
    - Keep existing fallback behavior when the cache or manifest is missing
    or invalid.
    
    ## Test Plan
    - `cd codex-rs && just fmt`
    - `cd codex-rs && cargo test -p codex-core-plugins
    list_marketplaces_installed_git_source_reads_metadata_from_cache_without_cloning`
    - `cd codex-rs && cargo test -p codex-app-server
    plugin_list_returns_installed_git_source_interface_from_cache`
    - `cd codex-rs && just fix -p codex-core-plugins`
    - `cd codex-rs && just fix -p codex-app-server`
    - `git diff --check`
    
    Server-truth check: OpenAI monorepo app-server generated types already
    expose `PluginSummary.interface`, and the webview consumes it for plugin
    cards. This PR keeps the protocol/schema unchanged and fills the
    existing field from the cached installed bundle for Git-backed
    cross-repo plugins.
  • feat: Add role-aware plugin share context APIs (#21867)
    Expose discoverability and full share principals in share context, carry
    roles through save/updateTargets, hydrate local shared plugin reads, and
    keep share URLs only under plugin.shareContext.
  • Move file watcher out of core (#21290)
    ## Why
    
    The app-server watcher relocation leaves the generic filesystem watcher
    as the last watcher-specific implementation still living inside
    `codex-core`. Moving that code to a small crate keeps `codex-core`
    focused on thread execution and lets app-server depend on the watcher
    without reaching back into core for filesystem watching primitives.
    
    This PR is stacked on #21287.
    
    ## What changed
    
    - Added a new `codex-file-watcher` crate containing the existing watcher
    implementation and its unit tests.
    - Updated app-server `fs_watch`, `skills_watcher`, and listener state to
    import watcher types from `codex-file-watcher`.
    - Removed the `file_watcher` module and `notify` dependency from
    `codex-core`.
    - Updated Cargo workspace metadata and `Cargo.lock` for the new internal
    crate.
    
    ## Validation
    
    - `cargo check -p codex-file-watcher -p codex-core -p codex-app-server`
    - `cargo test -p codex-file-watcher`
    - `cargo test -p codex-app-server
    skills_changed_notification_is_emitted_after_skill_change`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-file-watcher`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
  • Reapply "Move skills watcher to app-server" (#21652)
    ## Why
    
    PR #21460 reverted the earlier move of skills change watching from
    `codex-core` into app-server. This reapplies that boundary change so
    app-server owns client-facing `skills/changed` notifications and core no
    longer carries the watcher.
    
    ## What
    
    - Restore the app-server `SkillsWatcher` and register it from thread
    listener setup.
    - Remove the core-owned skills watcher and its core live-reload
    integration surface.
    - Restore app-server coverage for `skills/changed` notifications after a
    watched skill file changes.
    
    ## Validation
    
    - `cargo test -p codex-app-server --test all
    suite::v2::skills_list::skills_changed_notification_is_emitted_after_skill_change
    -- --exact --nocapture`
    - `cargo test -p codex-core --lib --no-run`
  • [codex] support executor registry remote environments (#21323)
    ## Summary
    
    Support registry-backed remote executors end to end so downstream
    services can resolve an executor id into an exec-server URL and make
    that environment available to Codex without relying on the legacy cloud
    environments flow.
    
    ## What changed
    
    - switch remote executor registration to the executor registry bootstrap
    contract
    - allow named remote environments to be inserted into
    `EnvironmentManager` at runtime
    - add the experimental app-server RPC `environment/add` so initialized
    experimental clients can register those remote environments for later
    `thread/start` and `turn/start` selection
    
    ## Validation
    
    Ran focused validation locally:
    - `cargo test -p codex-exec-server environment_manager_`
    - `cargo test -p codex-exec-server
    register_executor_posts_with_bearer_token_header`
    - `cargo test -p codex-app-server-protocol`
  • app-server: support daemon-safe restart handling (#21831)
    ## Why
    
    The app-server daemon work needs two app-server behaviors to be safe
    when lifecycle management is driven by a helper process:
    
    - a readiness probe must not become the process-wide client identity
    just because it connects first
    - a graceful reload signal needs to keep draining active turns even if
    it is delivered more than once
    
    ## What changed
    
    - Treat `codex_app_server_daemon` initialization as a probe-only client
    for process-global originator and user-agent suffix state.
    - Distinguish forceable shutdown signals from graceful-only ones, and
    treat Unix `SIGHUP` as graceful-only while leaving `SIGTERM` and Ctrl-C
    forceable.
    - Add regression coverage for daemon probe initialization and repeated
    `SIGHUP` delivery while a turn is still running.
    
    ## Testing
    
    - `cargo test -p codex-app-server`
      - The new daemon-probe and repeated-`SIGHUP` coverage passed.
    - The run still failed in the existing
    `suite::conversation_summary::get_conversation_summary_by_relative_rollout_path_resolves_from_codex_home`
    and
    `suite::conversation_summary::get_conversation_summary_by_thread_id_reads_rollout`
    tests because their initialize handshake timed out.
    - `cargo test -p codex-app-server --test all
    suite::conversation_summary::`
    - Reproduced the same two existing initialize-timeout failures in
    isolation.
  • Make environment provider snapshots path-free (#21794)
    ## Summary
    - make EnvironmentProvider::snapshot path-free and keep providers
    focused on provider-owned remote environments
    - let provider snapshots request local inclusion via include_local, with
    environments.toml including local and CODEX_EXEC_SERVER_URL excluding
    local
    - move reserved local environment construction into EnvironmentManager
    using ExecServerRuntimePaths
    
    Follow-up to https://github.com/openai/codex/pull/20667
    
    ## Testing
    - just fmt
    - git diff --check
    - devbox: bazel build --bes_backend= --bes_results_url=
    //codex-rs/exec-server:exec-server
    - devbox: bazel test --bes_backend= --bes_results_url=
    //codex-rs/exec-server:exec-server-unit-tests
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] request desktop attestation from app (#20619)
    ## Summary
    
    TL;DR: teaches `codex-rs` / app-server to request a desktop-provided
    attestation token and attach it as `x-oai-attestation` on the scoped
    ChatGPT Codex request paths.
    
    ![DeviceCheck attestation
    interface](https://raw.githubusercontent.com/openai/codex/dev/jm/devicecheck-diagram-assets/pr-assets/devicecheck-attestation-interface.png)
    
    ## Details
    
    This PR teaches the Codex app-server runtime how to request and attach
    an attestation token. It does not generate DeviceCheck tokens directly;
    instead, it relies on the connected desktop app to advertise that it can
    generate attestation and then asks that app for a fresh header value
    when needed.
    
    The flow is:
    
    1. The Codex desktop app connects to app-server.
    2. During `initialize`, the app can advertise that it supports
    `requestAttestation`.
    3. Before app-server calls selected ChatGPT Codex endpoints, it sends
    the internal server request `attestation/generate` to the app.
    4. app-server receives a pre-encoded header value back.
    5. app-server forwards that value as `x-oai-attestation` on the scoped
    outbound requests.
    
    The code in this repo is mostly protocol and runtime plumbing: it adds
    the app-server request/response shape, introduces an attestation
    provider in core, wires that provider into Responses / compaction /
    realtime setup paths, and covers the intended scoping with tests. The
    signed macOS DeviceCheck generation remains owned by the desktop app PR.
    
    ## Related PR
    
    - Codex desktop app implementation:
    https://github.com/openai/openai/pull/878649
    
    ## Validation
    
    <details>
    <summary>Tests run</summary>
    
    ```sh
    cargo test -p codex-app-server-protocol
    cargo test -p codex-core attestation --lib
    cargo test -p codex-app-server --lib attestation
    ```
    
    Also ran:
    
    ```sh
    just fix -p codex-core
    just fix -p codex-app-server
    just fix -p codex-app-server-protocol
    just fmt
    just write-app-server-schema
    ```
    
    </details>
    
    <details>
    <summary>E2E DeviceCheck validation</summary>
    
    First validated the signed desktop app boundary directly: launched a
    packaged signed `Codex.app`, sent `attestation/generate`, decoded the
    returned `v1.` attestation header, and validated the extracted
    DeviceCheck token with `personal/jm/verify_devicecheck_token.py` using
    bundle ID `com.openai.codex`. Apple returned `status_code: 200` and
    `is_ok: true`.
    
    Then ran the fuller app + app-server flow. The packaged `Codex.app`
    launched a current-branch app-server via `CODEX_CLI_PATH`, and a local
    MITM proxy intercepted outbound `chatgpt.com` traffic. The app-server
    requested `attestation/generate` from the real Electron app process, and
    the intercepted `/backend-api/codex/responses` traffic included
    `x-oai-attestation` on both routes:
    
    ```text
    GET  /backend-api/codex/responses  Upgrade: websocket  x-oai-attestation: present
    POST /backend-api/codex/responses  Upgrade: none       x-oai-attestation: present
    ```
    
    The captured header decoded to a DeviceCheck token that also validated
    with Apple for `com.openai.codex` (`status_code: 200`, `is_ok: true`,
    team `2DC432GLL2`).
    
    </details>
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Load configured environments from CODEX_HOME (#20667)
    ## Why
    
    The earlier PRs add stdio transport support and the config-backed
    environment provider, but the feature remains inert until normal Codex
    entrypoints construct `EnvironmentManager` with enough context to
    discover `CODEX_HOME/environments.toml`. This final stack PR activates
    the provider while preserving the legacy `CODEX_EXEC_SERVER_URL`
    fallback when no environments file exists.
    
    **Stack position:** this is PR 5 of 5. It is the product wiring PR that
    activates the configured environment provider added in PR 4.
    
    ## What Changed
    
    - Thread `codex_home` into `EnvironmentManagerArgs`.
    - Change `EnvironmentManager::new(...)` to load the provider from
    `CODEX_HOME`.
    - Preserve legacy behavior by falling back to
    `DefaultEnvironmentProvider::from_env()` when `environments.toml` is
    absent.
    - Make `environments.toml`-backed managers start new threads with all
    configured environments, default first, while keeping the legacy env-var
    path single-default.
    - Update the app-server, TUI, exec, MCP server, connector, prompt-debug,
    and thread-manager-sample callsites to pass `codex_home` and handle
    provider-loading errors.
    
    ## Self-Review Notes
    
    - The multi-environment startup path is intentionally tied to the
    `environments.toml` provider. Using `>1` configured environment as the
    only signal would also expand the legacy `CODEX_EXEC_SERVER_URL`
    provider because it keeps `local` addressable alongside `remote`.
    - The startup environment list is still derived inside
    `EnvironmentManager`; the provider only says whether its snapshot should
    start new threads with all configured environments.
    - The thread-manager sample was updated to pass the current
    `ThreadManager::new(...)` installation id argument so the stack compiles
    under Bazel.
    
    ## Stack
    
    - 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server
    listener
    - 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server
    client transport
    - 3. https://github.com/openai/codex/pull/20665 - Make environment
    providers own default selection
    - 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME
    environments TOML provider
    - **5. This PR:** https://github.com/openai/codex/pull/20667 - Load
    configured environments from CODEX_HOME
    
    Split from original draft: https://github.com/openai/codex/pull/20508
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `bazel build --config=remote --strategy=remote
    --remote_download_toplevel
    //codex-rs/thread-manager-sample:codex-thread-manager-sample`
    - `bazel test --config=remote --strategy=remote
    --remote_download_toplevel
    //codex-rs/exec-server:exec-server-unit-tests`
    - `bazel test --config=remote --strategy=remote
    --remote_download_toplevel --test_sharding_strategy=disabled
    --test_arg=default_thread_environment_selections_use_manager_default_id
    //codex-rs/core:core-unit-tests`
    - `bazel test --config=remote --strategy=remote
    --remote_download_toplevel --test_sharding_strategy=disabled
    --test_arg=start_thread_uses_all_default_environments_from_codex_home
    //codex-rs/core:core-unit-tests`
    
    ## Documentation
    
    This activates `CODEX_HOME/environments.toml`; user-facing documentation
    should be added before this stack is treated as a documented public
    workflow.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Use installation ID in remote enrollments (#21662)
    * Pass installation ID for storage on enrollments server for
    deduping/grouping multiple appservers per installation
    * Pass installation ID in remoteControl/status/changed events
  • [codex] Generalize service tier slash commands (#21745)
    ## Why
    
    `/fast` was wired as a one-off slash command even though model metadata
    now exposes service tiers as catalog data. That meant adding another
    tier, such as a slower/cheaper tier, would require more hardcoded TUI
    plumbing instead of letting the model catalog drive the available
    commands.
    
    This change makes service-tier commands data-driven: each advertised
    `service_tiers` entry becomes a `/name` command using the catalog
    description, while the request path sends the tier `id` only when the
    selected model supports it.
    
    ## What Changed
    
    - Removed the hardcoded `/fast` slash-command variant and introduced
    dynamic service-tier command items in the composer and command popup.
    - Added toggle behavior for service-tier commands: invoking `/name`
    selects that tier, and invoking it again clears the selection.
    - Preserved the existing Fast-mode keybinding/status affordances by
    resolving the current model tier whose name is `fast`, while still
    sending the tier request value such as `priority`.
    - Persisted service-tier selections as raw request strings so non-fast
    tiers can round-trip through config.
    - Updated the Bedrock catalog entry to advertise fast support through
    `service_tiers` with `id: "priority"` and `name: "fast"`.
    - Added defensive filtering in core so unsupported selected service
    tiers are omitted from `/responses` requests.
    
    ## Validation
    
    - Added/updated coverage for dynamic service-tier slash command lookup,
    popup descriptions, composer dispatch, TUI fast toggling, and
    unsupported-tier omission in core request construction.
    - Local tests were not run per request.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Update plugin share settings with discoverability (#21637)
    Requires discoverability on plugin/share/updateTargets so the server can
    manage workspace link access consistently, including auto-adding the
    workspace principal for UNLISTED.
    
    Also rejects LISTED on share creation and blocks client-supplied
    workspace principals while preserving response parsing for LISTED.
  • Remove skills list extra roots (#21485)
    ## Summary
    - Remove `perCwdExtraUserRoots` / `SkillsListExtraRootsForCwd` from the
    `skills/list` app-server API.
    - Drop Rust app-server and `codex-core-skills` extra-root plumbing so
    skill scans are keyed by the normal cwd/user/plugin roots only.
    - Regenerate app-server schemas and update docs/tests that only existed
    for the removed extra-roots behavior.
    
    ## Validation
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core-skills`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core-skills`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
    
    ## Notes
    - `cargo test -p codex-app-server --test all skills_list` ran the edited
    skills-list cases, but the full filtered run ended on existing
    `skills_changed_notification_is_emitted_after_skill_change` timeout
    after a websocket `401`.
    - `cargo test -p codex-tui --lib` compiled the changed TUI callers, then
    failed two unrelated status permission tests because local
    `/etc/codex/requirements.toml` forbids `DangerFullAccess`.
    - Source-truth check found the OpenAI monorepo still has
    generated/app-server-kit mirror references to the removed field; those
    should be cleaned up when generated app-server types are synced or in a
    companion OpenAI cleanup.
  • [codex-analytics] plumb protocol-native review timing (#21434)
    ## Why
    
    We want terminal tool review analytics, but the reducer should not stamp
    review timing from its own wall clock.
    
    This PR plumbs review timing through the real protocol and app-server
    seams so downstream analytics can consume the emitter's timestamps
    directly. Guardian reviews keep their enriched `started_at` /
    `completed_at` analytics fields by deriving those legacy second-based
    values from the same protocol-native millisecond lifecycle timestamps,
    rather than sampling a separate analytics clock.
    
    ## What changed
    
    - add `started_at_ms` to user approval request payloads
    - add `started_at_ms` / `completed_at_ms` to guardian review
    notifications
    - preserve Guardian review `started_at` / `completed_at` enrichment from
    the protocol-native timing source
    - stamp typed `ServerResponse` analytics facts with app-server-observed
    `completed_at_ms`
    - thread the new timing fields through core, protocol, app-server, TUI,
    and analytics fixtures
    
    ## Verification
    
    - `cargo test -p codex-app-server outgoing_message --manifest-path
    codex-rs/Cargo.toml`
    - `cargo test -p codex-app-server-protocol guardian --manifest-path
    codex-rs/Cargo.toml`
    - `cargo test -p codex-tui guardian --manifest-path codex-rs/Cargo.toml`
    - `cargo test -p codex-analytics analytics_client_tests --manifest-path
    codex-rs/Cargo.toml`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21434).
    * #18748
    * __->__ #21434
    * #18747
    * #17090
    * #17089
    * #20514
  • [codex] Remove remote thread store implementation (#21596)
    Remove the remote thread-store backend and checked-in protobuf
    artifacts. We've moved these into another crate that link against this
    one.
    
    Also remove the config settings for thread store backend selection,
    since we'll instead pass an instantiated thread store into the core-api
    crate's main entrypoint.
  • feat(app-server, threadstore): Thread pagination APIs and ThreadStore contract (#21566)
    ## Why
    The goal of this PR is to align on app-server and `ThreadStore` API
    updates for paginating through large threads.
    
    
    #### app-server
    ##### `thread/turns/list`
    - Updates `thread/turns/list` to support `itemsView?: "notLoaded" |
    "summary" | "full" | null`, defaulting to `summary`.
    - Implements the current `thread/turns/list` behavior over the existing
    persisted rollout-history fallback:
      - `notLoaded` returns turn envelopes with empty `items`.
    - `summary` returns the first user message and final assistant message
    when available.
      - `full` preserves the existing full item behavior.
    
    Note that this method still uses the naive approach of loading the
    entire rollout file, and returns just the filtered slice of the data.
    Real pagination will come later by leveraging SQLite.
    
    ##### `thread/turns/items/list`
    - Adds the experimental `thread/turns/items/list` protocol, schema,
    dispatcher, and processor stub. The app-server currently returns
    JSON-RPC `-32601` with `thread/turns/items/list is not supported yet`.
    
    #### ThreadStore
    - Adds the experimental `thread/turns/items/list` protocol, schema,
    dispatcher, and processor stub. The app-server currently returns
    JSON-RPC `-32601` with `thread/turns/items/list is not supported yet`.
    - Adds `ThreadStore` contract types and stubbed methods for listing
    thread turns and listing items within a turn.
    - Adds a typed `StoredTurnStatus` and `StoredTurnError` to avoid baking
    app-server API enums or lossy string status values into the store-facing
    turn contract.
    - Adds a typed `StoredTurnStatus` and `StoredTurnError` to avoid baking
    app-server API enums or lossy string status values into the store-facing
    turn contract.
    
    This also sketches the storage abstraction we expect to need once turns
    are indexed/stored. In particular, `notLoaded` is useful only if
    ThreadStore can eventually list turn metadata without loading every
    persisted item for each turn.
    
    ## Validation
    
    - Added/updated protocol serialization coverage for the new request and
    response shapes.
    - Added app-server integration coverage for `thread/turns/list` default
    summary behavior and all three `itemsView` modes.
    - Added app-server integration coverage that `thread/turns/items/list`
    returns the expected unsupported JSON-RPC error when experimental APIs
    are enabled.
    - Added thread-store coverage that the default trait methods return
    `ThreadStoreError::Unsupported`.
    
    No developers.openai.com documentation update is needed for this
    internal experimental app-server API surface.
  • Disable empty Cargo test targets (#21584)
    ## Summary
    
    `cargo test` has entails both running standard Rust tests and doctests.
    It turns out that the doctest discovery is fairly slow, and it's a cost
    you pay even for crates that don't include any doctests.
    
    This PR disables doctests with `doctest = false` for crates that lack
    any doctests.
    
    For the collection of crates below, this speeds up test execution by
    >4x.
    
    E.g., before this PR:
    
    ```
    Benchmark 1: cargo test     -p codex-utils-absolute-path     -p codex-utils-cache     -p codex-utils-cli     -p codex-utils-home-dir     -p codex-utils-output-truncation     -p codex-utils-path     -p codex-utils-string     -p codex-utils-template     -p codex-utils-elapsed     -p codex-utils-json-to-toml
      Time (mean ± σ):      1.849 s ±  4.455 s    [User: 0.752 s, System: 1.367 s]
      Range (min … max):    0.418 s … 14.529 s    10 runs
    ```
    
    And after:
    
    ```
    Benchmark 1: cargo test     -p codex-utils-absolute-path     -p codex-utils-cache     -p codex-utils-cli     -p codex-utils-home-dir     -p codex-utils-output-truncation     -p codex-utils-path     -p codex-utils-string     -p codex-utils-template     -p codex-utils-elapsed     -p codex-utils-json-to-toml
      Time (mean ± σ):     428.6 ms ±   6.9 ms    [User: 187.7 ms, System: 219.7 ms]
      Range (min … max):   418.0 ms … 436.8 ms    10 runs
    ```
    
    For a single crate, with >2x speedup, before:
    
    ```
    Benchmark 1: cargo test -p codex-utils-string
      Time (mean ± σ):     491.1 ms ±   9.0 ms    [User: 229.8 ms, System: 234.9 ms]
      Range (min … max):   480.9 ms … 512.0 ms    10 runs
    ```
    
    And after:
    
    ```
    Benchmark 1: cargo test -p codex-utils-string
      Time (mean ± σ):     213.9 ms ±   4.3 ms    [User: 112.8 ms, System: 84.0 ms]
      Range (min … max):   206.8 ms … 221.0 ms    13 runs
    ```
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Fix pathless thread summaries (#21266)
    ## Summary
    
    Fix `getConversationSummary` so thread-id summaries work for stored
    threads that do not have a local rollout path, such as remote thread
    stores.
    
    The root cause was that `summary_from_stored_thread` returned `None`
    when `StoredThread.rollout_path` was absent, and
    `get_thread_summary_response_inner` treated that as an internal error.
    This made conversation-id lookups depend on a local-only field even
    though the thread store can address the thread by id.
  • Move thread name edits to ThreadStore (#21264)
    - Route live thread renames through `ThreadStore` metadata updates.
    - Read resumed thread names from store metadata with legacy local
    fallback preserved in the store.
  • Upgrade cargo-shear to 1.11.2 (#21547)
    ## Summary
    
    Catches a few additional dependencies (`sha2`, `url`) that should be in
    `dev-dependencies`.
  • app-server: refresh live threads from latest config snapshot (#21187)
    ## Why
    
    App-server config writes were leaving existing threads partially stale.
    After a config mutation, the app-server told each live thread to run
    `Op::ReloadUserConfig`, but that path only re-read the user
    `config.toml` layer. Settings that came from the app-server's
    materialized config snapshot did not propagate to existing threads until
    restart.
    
    This change prevent a FS access from `core` for CCA.
    
    ## What changed
    
    - add `CodexThread::refresh_runtime_config()` and
    `Session::refresh_runtime_config()` so the app-server can push a freshly
    rebuilt config snapshot into a live thread
    - rebuild the latest config with each thread's `cwd` after config
    mutations, then refresh the thread from that snapshot instead of asking
    it to reload only `config.toml`
    - keep session-static settings unchanged during refresh, while updating
    runtime-refreshable state such as the config layer stack,
    `tool_suggest`, and derived hook/plugin/skill state
    - keep `reload_user_config_layer()` as the file-backed fallback for
    legacy local reload flows, but route the shared refresh logic through
    the new runtime refresh path
    
    ## Testing
    
    - add a session test that verifies `refresh_runtime_config()` rebuilds
    hooks from refreshed config
    - add a session test that verifies runtime-refreshable fields update
    while session-static settings like `model` and `notify` stay unchanged
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Expose plugin share metadata in shareContext (#21495)
    Extends PluginSummary.shareContext with shareUrl and reader shareTargets
  • [codex] add account id to feedback uploads (#21498)
    ## Why
    
    Feedback uploads already carry auth-derived context like
    `chatgpt_user_id`, but they do not include the authenticated
    workspace/account id. Adding `account_id` makes feedback triage easier
    when a user can operate across multiple ChatGPT workspaces.
    
    ## What changed
    
    - emit auth-derived `account_id` into feedback tags in `app-server`
    before the feedback snapshot is uploaded
    - preserve that tag through `codex-feedback` upload tag assembly
    alongside the existing merge behavior for other tags
    - extend `codex-feedback` coverage to assert that snapshot-derived
    `account_id` is present in uploaded tags
    
    ## Verification
    
    - `cargo test -p codex-feedback
    upload_tags_include_client_tags_and_preserve_reserved_fields`
    - `cargo test -p codex-app-server --lib feedback_processor`
  • Show plugin hooks in plugin details (#21447)
    Supersedes the abandoned #19859, rebuilt on latest `main`.
    
    # Why
    
    PR #19705 adds discovery for hooks bundled with plugins, but `/plugins`
    still only shows skills, apps, and MCP servers. This follow-up makes
    bundled hooks visible in the same plugin detail view so users can
    inspect the full plugin surface in one place.
    
    We also need `PluginHookSummary` to populate Plugin Hooks in the app;
    `hooks/list` is not enough there because plugin detail needs to show
    hooks for disabled plugins too.
    
    # What
    
    - extend `plugin/read` with `PluginHookSummary` entries for bundled
    hooks
    - summarize plugin hooks while loading plugin details
    - render a `Hooks` row in the `/plugins` detail popup
    
    <img width="3456" height="848" alt="CleanShot 2026-04-27 at 11 45 34@2x"
    src="https://github.com/user-attachments/assets/fe3a38d6-a260-4351-8513-fb04c93d725b"
    />
  • Revert state DB injection and agent graph store (#21481)
    ## Why
    
    Reverts #20689 to restore the previous optional state DB plumbing. The
    conflict resolution keeps the newer installation ID and session/thread
    identity changes that landed after #20689, while removing the mandatory
    state DB and agent graph store dependency from ThreadManager
    construction.
    
    ## What changed
    
    - Restored `Option<StateDbHandle>` through app-server, MCP server,
    prompt debug, and test entry points.
    - Removed the `codex-core` dependency on `codex-agent-graph-store` and
    reverted descendant lookup back to the existing state DB path when
    available.
    - Kept newer `installation_id` forwarding by passing it beside the
    optional DB handle.
    - Kept local thread-name updates working when the optional state DB
    handle is absent.
    
    ## Validation
    
    - `git diff --check`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-state -p codex-rollout -p
    codex-app-server-protocol`
    - Attempted `env CARGO_INCREMENTAL=0 cargo test -p codex-core -p
    codex-app-server -p codex-app-server-client -p codex-mcp-server -p
    codex-thread-manager-sample -p codex-tui`; blocked locally by a rustc
    ICE while compiling `v8 v146.4.0` with `rustc 1.93.0 (254b59607
    2026-01-19)` on `aarch64-apple-darwin`.
  • [codex] Parallelize skills list cwd loading (#21441)
    ## Summary
    - process `skills/list` cwd entries with bounded concurrency of 5
    - preserve the caller's requested cwd order in the response
    - add coverage that verifies response ordering remains stable
    
    ## Why
    Cold-start desktop traces showed that `skills/list` can dominate the
    shared config queue when it scans many workspace roots serially. The
    expensive work is largely independent per cwd, so the request was paying
    the sum of all cwd costs instead of the cost of the slowest bounded
    batch.
    
    ## Impact
    This keeps current request semantics intact while reducing the
    wall-clock time of large multi-root `skills/list` calls. That should
    also reduce how long later config-family requests, such as
    `plugin/list`, wait behind `skills/list` during startup.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-app-server
    skills_list_preserves_requested_cwd_order`
  • [codex] allow shared config reads in app-server queue (#21340)
    ## Summary
    - add a shared-read serialization mode for global app-server request
    families
    - let consecutive leading shared reads for the same family run together
    while keeping exclusive requests ordered
    - mark only `skills/list`, `config/read` and `plugin/list` as shared
    reads for now
    
    ## Why
    `skills/list` and `plugin/list` are read-only config-family requests,
    but the app-server queue currently treats every config request as
    exclusive. That means one long `skills/list` can make a later
    `plugin/list` wait even though the two requests do not mutate config.
    
    This change keeps the existing queue order but lets adjacent reads
    overlap. If a write is already waiting, later reads still stay behind
    it, so writes do not starve.
    
    ## Scope
    This intentionally keeps the first pass narrow:
    - shared reads: `skills/list`, `plugin/list`
    - still exclusive: `plugin/install`, `marketplace/*`,
    `skills/config/write`, `config/*write`, `config/read`, and the rest of
    the config family
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    
    ## Desktop verification
    I ran the dev desktop app against this branch's built binary with the
    existing UI timing logs enabled. The app did use
    `/Users/xli/code/codex_6/codex-rs/target/debug/codex`.
    
    The new scheduler behavior works, but this narrow change does not remove
    every cold-start delay: in the observed trace, an earlier exclusive
    `config/read` was already queued ahead of the later `skills/list` and
    `plugin/list` requests, so the page-open plugin requests still waited
    behind that earlier exclusive config-family request before they could
    run together.
    
    That means this PR is the scheduler primitive needed for shared reads,
    not the complete end-to-end latency fix by itself.
    
    ## Not run
    - full workspace test suite, because repo policy requires explicit
    approval before running it after touching `app-server-protocol`
  • Add compact lifecycle hooks (started by vincentkoc - external contrib) (#19905)
    Based on work from Vincent K -
    https://github.com/openai/codex/pull/19060
    
    <img width="1836" height="642" alt="CleanShot 2026-04-29 at 20 47 40@2x"
    src="https://github.com/user-attachments/assets/b647bb89-65fe-40c8-80b0-7a6b7c984634"
    />
    
    ## Why
    
    Compaction rewrites the conversation context that future model turns
    receive, but hooks currently have no deterministic lifecycle point
    around that rewrite. This adds compact lifecycle hooks so users can
    audit manual and automatic compaction, surface hook messages in the UI,
    and run post-compaction follow-up without overloading tool or prompt
    hooks.
    
    ## What Changed
    
    - Added `PreCompact` and `PostCompact` hook events across hook config,
    discovery, dispatch, generated schemas, app-server notifications,
    analytics, and TUI hook rendering.
    - Added trigger matching for compact hooks with the documented `manual`
    and `auto` matcher values.
    - Wired `PreCompact` before both local and remote compaction, and
    `PostCompact` after successful local or remote compaction.
    - Kept compact hook command input to lifecycle metadata: session id,
    Codex turn id, transcript path, cwd, hook event name, model, and
    trigger.
    - Made compact stdout handling consistent with other hooks: plain stdout
    is ignored as debug output, while malformed JSON-looking stdout is
    reported as failed hook output.
    - Added integration coverage for compact hook dispatch, trigger
    matching, post-compact execution, and the audited behavior that
    `decision:"block"` does not block compaction.
    
    ## Out of Scope
    
    - Hook-specific compaction blocking is not implemented;
    `decision:"block"` and exit-code-2 blocking semantics are intentionally
    unsupported for `PreCompact`.
    - Custom compaction instructions are not exposed to compact hooks in
    this PR.
    - Compact summaries, summary character counts, and summary previews are
    not exposed to compact hooks in this PR.
    
    ## Verification
    
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-core
    manual_pre_compact_block_decision_does_not_block_compaction`
    - `cargo test -p codex-app-server hooks_list`
    - `cargo test -p codex-core config_schema_matches_fixture`
    - `cargo test -p codex-tui hooks_browser`
    
    ## Docs
    
    The developer documentation for Codex hooks should be updated alongside
    this feature to document `PreCompact` and `PostCompact`, the
    `manual`/`auto` matcher values, and the compact hook payload fields.
    
    ---------
    
    Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
  • feat: Add marketplace source filtering and plugin share context (#21419)
    Adds marketplaceKinds to plugin/list for local, workspace-directory, and
    shared-with-me; omitted params keep default local plus gated global
    behavior, while explicit kinds are exact.
    
    Exposes shareContext on plugin summaries from local share mappings and
    remote workspace/shared responses, including remotePluginId and nullable
    creator metadata.
    
    Adds shared-with-me listing through /ps/plugins/workspace/shared,
    renames the workspace remote namespace to workspace-directory, and keeps
    direct remote read/share/install/update/delete paths gated by plugins
    rather than remote_plugin.
  • Move skills watcher to app-server (#21287)
    ## Why
    
    Skills update notifications are app-server API behavior, but the watcher
    lived in `codex-core` and surfaced through
    `EventMsg::SkillsUpdateAvailable`. Moving the watcher out keeps core
    focused on thread execution and lets app-server own both cache
    invalidation and the `skills/changed` notification.
    
    ## What changed
    
    - Added an app-server-owned skills watcher that watches local skill
    roots, clears the shared skills cache, and emits `skills/changed`
    directly.
    - Registers skill watches from the common app-server thread listener
    attach path, including direct starts, resumes, and app-server-observed
    child or forked threads.
    - Stores the `WatchRegistration` on `ThreadState`, so listener
    replacement, thread teardown, idle unload, and app-server shutdown
    deregister by dropping the RAII guard.
    - Removed `EventMsg::SkillsUpdateAvailable`, the core watcher, and the
    old core live-reload test.
    - Extended the app-server skills change test to verify a cached skills
    list is refreshed after a filesystem change without forcing reload.
    
    ## Validation
    
    - `cargo check -p codex-core -p codex-app-server -p codex-mcp-server -p
    codex-rollout -p codex-rollout-trace`
    - `cargo test -p codex-app-server
    skills_changed_notification_is_emitted_after_skill_change`
  • [codex-analytics] emit tool item events from item lifecycle (#17090)
    ## Why
    
    After the tool-item schemas are in place, analytics needs to emit them
    from the app-server item lifecycle rather than requiring bespoke
    tracking at each callsite. The reducer should also reuse the shared
    thread analytics context introduced below it in the stack so later event
    families do not repeat the same reducer joins or missing-state ladder.
    
    ## What changed
    
    - Tracks tool-item completion notifications and emits the matching tool
    analytics event when a terminal item arrives.
    - Derives event-specific payload details for command execution, file
    changes, MCP calls, dynamic tools, collaboration tools, web search, and
    image generation.
    - Denormalizes thread, app-server client, runtime, and subagent
    provenance metadata through the shared thread analytics context.
    - Adds reducer coverage for item lifecycle emission and subagent
    metadata inheritance.
    
    ## Duration semantics
    
    `duration_ms` is computed from the app-server item lifecycle timestamps:
    `completed_at_ms - started_at_ms`. That makes it the duration of the
    lifecycle Codex observed locally, not necessarily the upstream
    provider's full execution time.
    
    - Web search usually has a meaningful observed lifecycle because
    Responses can send `response.output_item.added` before
    `response.output_item.done`; in that case `started_at_ms` comes from the
    added event and `completed_at_ms` comes from the done event.
    - Image generation can be much less precise. In the current observed
    stream, image generation often arrives only as a completed
    `response.output_item.done`; when there is no earlier added event, Codex
    synthesizes the started item immediately before completion, so
    `duration_ms` can be `0` even though upstream image generation took
    longer.
    - Standalone web search and standalone image generation work is expected
    to land after this stack. Those paths may introduce more direct
    lifecycle events or timing points, so the current
    web-search/image-generation duration semantics should be treated as the
    best available item-lifecycle approximation, not the final latency
    contract for those tool families.
    - `execution_duration_ms` is populated only where the completed item
    already carries a native execution duration; otherwise it remains `null`
    while `duration_ms` still reflects the local lifecycle interval.
    
    ## Currently placeholder / partial fields
    
    Some fields are included in the schema for the intended steady-state
    contract, but this PR does not yet populate them from real
    approval/review state:
    
    - `review_count`, `guardian_review_count`, and `user_review_count`
    currently default to `0`.
    - `final_approval_outcome` currently defaults to `unknown`.
    - `requested_additional_permissions` and `requested_network_access`
    currently default to `false`.
    
    ## Verification
    
    - `cargo test -p codex-analytics`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17090).
    * #18748
    * #18747
    * __->__ #17090
    * #17089
    * #20514
  • 2- Use string service tiers in session protocol (#20971)
    ## Summary
    - break service tier session/op/app-server protocol fields from the
    closed enum to string tier ids
    - send the service tier string directly through model requests, prewarm,
    compaction, memories, and TUI/app-server turn starts
    - regenerate app-server protocol JSON/TypeScript schemas, removing the
    standalone ServiceTier TS enum
    
    ## Verification
    - just fmt
    - cargo check -p codex-core -p codex-app-server -p codex-tui
    - just write-app-server-schema
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(app-server): move v2 sessionId onto Thread (#21336)
    ## Why
    
    `session_id` and `thread_id` are separate identities after #20437, but
    app-server only surfaced `sessionId` on the `thread/start`,
    `thread/resume`, and `thread/fork` response envelopes. Other
    thread-bearing surfaces such as `thread/list`, `thread/read`,
    `thread/started`, `thread/rollback`, `thread/metadata/update`, and
    `thread/unarchive` either lacked the grouping key or forced clients to
    special-case those three responses.
    
    Making `sessionId` part of the reusable `Thread` payload gives every v2
    API surface one place to expose session-tree identity.
    
    ## Mental model
      1. thread.sessionId lives on `Thread`
    2. It is a view/runtime identity for the current live session tree, not
    durable stored lineage metadata
    3. When app-server has a live loaded thread, it copies the real value
    from core’s session_configured.session_id
    4. When it only has stored/unloaded data, it falls back to
    thread.sessionId = thread.id
    
    ## What changed
    
    - Added `sessionId` to the v2
    [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109).
    - Removed the duplicate top-level `sessionId` fields from
    `thread/start`, `thread/resume`, and `thread/fork`; clients should now
    read `response.thread.sessionId`.
    - Populated `thread.sessionId` when building live thread responses,
    replaying loaded threads, and returning stored-thread summaries so the
    field is present across start, resume, fork, list, read, rollback,
    metadata-update, unarchive, and `thread/started` paths. See
    [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918)
    and
    [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719).
    - Preserved the stored-thread fallback: if a thread has not been loaded
    into a live session tree yet, `thread.sessionId` falls back to
    `thread.id`; once the thread is live again, the field reports the active
    session tree root.
    - Regenerated the JSON/TypeScript schemas and updated the app-server
    README examples to show
    [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310)
    on the thread object.
  • Move installation ID resolution out of core startup (#21182)
    ## Summary
    
    - resolve or inject the installation ID before core startup and pass it
    through `ThreadManager`, `CodexSpawnArgs`, and `Session` as a plain
    `String`
    - keep child sessions on the parent installation ID instead of
    rediscovering it inside core
    - propagate installation ID startup failures in `mcp-server` instead of
    panicking
    
    ## Why
    
    Core was still touching the filesystem on the session startup path to
    discover `installation_id`. This moves that work to the outer host
    boundary so core no longer depends on `codex_home` reads during session
    construction.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: return session ID from thread/fork (#21332)
    ## Why
    
    `thread/start` and `thread/resume` already return `sessionId`, but
    `thread/fork` only returned the new thread. That left clients to infer
    the forked thread's session identity from `thread.id`, which kept the
    new `session_id` / `thread_id` split implicit at one lifecycle boundary.
    Follow-up to #20437.
    
    ## What changed
    
    - Add `sessionId` to `ThreadForkResponse`.
    - Populate it from the forked session configuration.
    - Regenerate the v2 JSON/TypeScript schema fixtures and update the
    app-server docs/example.
    - Extend the fork integration test to assert the returned `sessionId`.
    
    ## Verification
    
    - Added coverage in `thread_fork_creates_new_thread_and_emits_started`
    for the new response field.
  • feat: add session_id (#20437)
    ## Summary
    
    Related to
    https://openai.slack.com/archives/C095U48JNL9/p1777537279707449
    TLDR:
    We update the meaning of session ids and thread ids:
    * thread_id stays as now
    * session_id become a shared id between every thread under a /root
    thread (i.e. every sub-agent share the same session id)
    
    This PR introduces an explicit `SessionId` and threads it through the
    protocol/client boundary so `session_id` and `thread_id` can diverge
    when they need to, while preserving compatibility for older serialized
    `session_configured` events.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Support Codex Apps auth elicitations (#19193)
    ## Summary
    
    - request URL-mode MCP elicitations when Codex Apps tool calls fail with
    connector auth metadata
    - route Codex Apps auth URL elicitations into the TUI app-link flow
    
    ## Test plan
    
    - `just fmt`
    - `cargo test -p codex-core mcp_tool_call::tests`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-tui bottom_pane::app_link_view::tests`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp`
    - `just fix -p codex-tui`
    
    Also attempted broader local runs:
    
    - `cargo test -p codex-core` fails in unrelated
    config/request-permission/proxy-sensitive tests under the current Codex
    Desktop environment.
    - `cargo test -p codex-tui` fails in unrelated status
    snapshots/trust-default tests because the ambient environment renders
    workspace-write/network permission defaults.
  • Preserve session MCP config on refresh (#21055)
    # Overview
    MCP refreshes were rebuilding active threads from fresh disk-backed
    config only, which dropped thread-start session overlays such as
    app-injected MCP servers. This keeps refreshes current with disk config
    while preserving the thread-local config that only the active thread
    knows about.
    
    # Changes
    - Rebuild refreshed config per active thread using that thread's current
    `cwd`, rather than fanning out one app-server config to every thread.
    - Preserve each thread's `SessionFlags` layer while replacing reloadable
    config layers with freshly loaded config, then derive the MCP refresh
    payload from the rebuilt result.
    - Move MCP refresh orchestration into app-server so manual refreshes
    fail loudly while background refreshes remain best-effort, and route
    plugin-triggered refreshes through the same per-thread reload path.
    - Add regression coverage for session overlays, fresh project config,
    plugin-derived MCP config, current requirements, and strict vs
    best-effort refresh behavior.
    
    # Verification
    - Passed focused Rust coverage for the thread-config rebuild behavior
    and deferred MCP refresh flow, plus `cargo test -p codex-app-server
    --lib`.
    - Verified end to end in the Codex dev app against the locally built
    CLI: registered an MCP via thread config, verified that it could be used
    successfully before refresh, manually triggered MCP refresh, and
    verified that it continued to be available afterward.