Commit Graph

6287 Commits

  • Fix duplicate CLI issue template description (#21685)
    Fixes #21270.
    
    The CLI bug report template defined `description` twice for the terminal
    emulator field. Because duplicate YAML keys are ambiguous and parsers
    generally keep the later value, the form could drop the multiplexer
    guidance.
    
    This combines that guidance with the terminal examples under a single
    block scalar in `.github/ISSUE_TEMPLATE/3-cli.yml`.
  • 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.
  • feat: enable AWS login credentials for Bedrock auth (#21623)
    ## Summary
    
    Codex's Amazon Bedrock provider signs Mantle requests with SigV4 using
    credentials resolved by the AWS SDK. That worked for standard AWS
    profiles and environment credentials, but AWS CLI console-login profiles
    created by `aws login` require the SDK's `credentials-login` feature to
    resolve `login_session` credentials.
    
    This change enables that credential provider so Bedrock can use AWS
    console-login credentials through the existing provider-owned AWS auth
    path.
    
    While testing the console-login path, we also hit a Mantle-specific
    SigV4 regression from the new split between `session_id` and
    `thread_id`. Mantle does not preserve legacy OpenAI compatibility
    headers that use `snake_case` before SigV4 verification, so signing
    those headers can make the server reconstruct a different canonical
    request. The Bedrock auth path now removes that header class before
    signing, keeping preserved hyphenated Codex/AWS headers such as
    `x-codex-turn-metadata` signed normally.
    
    ## Changes
    
    - Enable `aws-config`'s `credentials-login` feature in
    `codex-rs/aws-auth`.
    - Add a compile-time regression test for
    `aws_config::login::LoginCredentialsProvider`.
    - Strip `snake_case` compatibility headers from Bedrock Mantle SigV4
    requests before signing.
    - Expand the Bedrock auth regression test to cover `session_id`,
    `thread_id`, and future headers of the same shape.
    - Refresh Cargo and Bazel lockfiles for the added `aws-sdk-signin`
    dependency.
    
    ## Tests
    - tested with `aws login` locally and verified that it works as
    intended.
  • 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
  • Send response.processed after remote compaction v2 (#21642)
    ## Why
    
    Remote compaction v2 consumes a normal Responses stream, but that
    compaction-specific stream consumer dropped the `response.completed` id.
    As a result, the `responses_websocket_response_processed` lifecycle
    notification was emitted for normal turn sampling but not after a v2
    remote compaction response was fully processed.
    
    ## What changed
    
    - Return the completed response id alongside the v2 `context_compaction`
    output item.
    - After v2 compacted history is installed, send `response.processed`
    through the same websocket session when the feature is enabled.
    - Add websocket regression coverage for a remote compaction v2 request
    followed by `response.processed`.
    
    ## Verification
    
    - `cargo test -p codex-core --test all
    responses_websocket_sends_response_processed_after_remote_compaction_v2
    -- --nocapture`
    - `cargo test -p codex-core
    collect_context_compaction_output_accepts_additional_output_items --
    --nocapture`
  • Add CODEX_HOME environments TOML provider (#20666)
    ## Why
    
    After stdio transports and provider-owned defaults exist, Codex needs a
    config-backed provider that can describe more than the single legacy
    `CODEX_EXEC_SERVER_URL` remote. This PR adds that provider without
    activating it in product entrypoints yet, keeping parser/validation
    review separate from runtime wiring.
    
    **Stack position:** this is PR 4 of 5. It builds on PR 3's
    provider/default model and adds the `environments.toml` provider used by
    PR 5.
    
    ## What Changed
    
    - Add `environment_toml.rs` as the TOML-specific home for parsing,
    validation, and provider construction.
    - Keep the TOML schema/provider structs private; the public constructor
    added here is `EnvironmentManager::from_codex_home(...)`.
    - Add `TomlEnvironmentProvider`, including validation for:
      - reserved ids such as `local` and `none`
      - duplicate ids
      - unknown explicit defaults
      - empty programs or URLs
      - exactly one of `url` or `program` per configured environment
    - Support websocket environments with `url = "ws://..."` / `wss://...`.
    - Support stdio-command environments with `program = "..."`.
    - Add helpers to load `environments.toml` from `CODEX_HOME`, but do not
    wire entrypoints to call them yet.
    - Add the `toml` dependency for parsing.
    
    ## 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. This PR:** https://github.com/openai/codex/pull/20666 - Add
    CODEX_HOME environments TOML provider
    - 5. 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
    
    Not run locally; this was split out of the original draft stack.
    
    ## Documentation
    
    This introduces the config shape for `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>
  • Route view_image through selected environments
    Route view_image through selected environments so image reads use the selected turn environment and cwd, with schema exposure limited to multi-environment toolsets.\n\nCo-authored-by: Codex <noreply@openai.com>
  • Make environment providers own default selection (#20665)
    ## Why
    
    The next PR in this stack introduces configured environments, where the
    provider knows both which environments exist and which one should be
    selected by default. The existing manager derived the default internally
    by checking for the legacy `remote` and `local` ids, and it treated
    "remote" as equivalent to "has a websocket URL." That does not work
    cleanly for stdio-command remotes because they are remote environments
    without an `exec_server_url`.
    
    **Stack position:** this is PR 3 of 5. It is the environment-model
    bridge between PR 2's transport enum and PR 4's TOML provider.
    
    ## What Changed
    
    - Add `DefaultEnvironmentSelection` to the `EnvironmentProvider`
    contract:
      - `Derived` preserves the old `remote`-then-`local` fallback behavior.
    - `Environment(id)` lets a provider explicitly select a configured
    default.
    - `Disabled` lets a provider intentionally expose no default
    environment.
    - Move the legacy `CODEX_EXEC_SERVER_URL=none` default-disabling
    behavior into `DefaultEnvironmentProvider`.
    - Make `EnvironmentManager` validate explicit provider defaults and
    return an error if the selected id is missing.
    - Track `remote_transport` separately from `exec_server_url` so
    stdio-command environments are still recognized as remote.
    - Add `Environment::remote_stdio_shell_command(...)` for the TOML
    provider added in the next PR.
    
    ## 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. This PR:** 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. 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
    
    Not run locally; this was split out of the original draft stack.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [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.
  • Add stdio exec-server client transport (#20664)
    ## Why
    
    Configured environments need to connect to exec-server instances that
    are not necessarily already listening on a websocket URL. A
    command-backed stdio transport lets Codex start an exec-server process,
    speak JSON-RPC over its stdio streams, and clean up that child process
    with the client lifetime.
    
    **Stack position:** this is PR 2 of 5. It builds on the server-side
    stdio listener from PR 1 and provides the client transport used by later
    environment/config PRs.
    
    ## What Changed
    
    - Add `ExecServerTransport` variants for websocket URLs and stdio shell
    commands.
    - Add stdio command connection support for `ExecServerClient`.
    - Move websocket/stdio transport setup into `client_transport.rs` so
    `client.rs` stays focused on shared JSON-RPC client, session, HTTP, and
    notification behavior.
    - Tie stdio child process cleanup to the JSON-RPC connection lifetime
    with a RAII lifetime guard.
    - Keep existing websocket environment behavior by adapting URL-backed
    remotes to `ExecServerTransport::WebSocketUrl`.
    
    ## Stack
    
    - 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server
    listener
    - **2. This PR:** 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. 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
    
    Not run locally; this was split out of the original draft stack and then
    refactored to separate transport setup from the base client.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Use --locked in cargo build and lint invocations (#21602)
    This ensures CI fails if the committed lockfile is outdated
  • [codex] Apply a Dependabot cooldown of 7 days (#21599)
    This adds 7-day cooldowns to all of our Dependabot ecosystem blocks. Our
    Dependabot runs will continue at the same cadence as before, but the
    scheduled PRs will no suggest updates that are fewer than 7 days old
    themselves. This serves two purposes: to let dependencies "bake" for a
    bit in terms of stability before we adopt them, and to give third-party
    security services/tooling a chance to detect and revoke malware.
    
    This should have no functional changes/consequences besides how rapidly
    we get (non-security) updates. Dependabot security PRs can still be
    scheduled and will bypass the cooldown.
  • codex-otel: add configurable trace metadata (#21556)
    Add Codex config for static trace span attributes and structured W3C
    tracestate field upserts. The config flows through OtelSettings so
    callers can attach trace metadata without touching every span call site.
    
    Apply span attributes with an SDK span processor so every exported
    trace span carries the configured metadata. Model tracestate as nested
    member fields so configured keys can be upserted while unrelated
    propagated state in the same member is preserved.
    
    Validate configured tracestate before installing provider-global state,
    including header-unsafe values the SDK does not reject by itself. This
    keeps Codex from propagating malformed trace context from config.
    
    Update the config schema, public docs, and OTLP loopback coverage for
    config parsing, span export, propagation, and invalid-header rejection.
  • 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>
  • Ensure all mentions of cargo-install are --locked (#21592)
    There's already a preference for this in the codebase, but a few of them
    have drifted away. Generally `--locked` is preferred to reduce exposure
    to supply-chain attacks (and just generally improve reproducibility).
    
    In an ideal world these dependencies would maybe even be pinned to
    versions but Cargo is kinda bad at that for devtools. Still better to
    use --locked than not.
  • [codex] Fully qualify hash-pins in GitHub Actions (#21436)
    This builds on top of https://github.com/openai/codex/pull/15828 by
    ensuring that hash-pinned actions with version comments are fully
    qualified, rather than referencing floating/mutable comments like "v7".
    This makes actions management tools behave more consistently.
    
    This shouldn't break anything, since it's comment only. But if it does,
    ping ww@ 🙂
  • Add a Cargo build profile for benchmarking (#21574)
    A clean release build takes ~18m and an incremental build takes ~12m.
    This is far too slow to iterate on performance related changes and the
    build time is dominated by LTO.
    
    This pull request adds a `profiling` profile for Cargo which takes ~13m
    clean and ~6m incremental, the primary change is that LTO is disabled.
    This matches a profile used in uv and follows the great work at
    https://github.com/astral-sh/uv/pull/5955 — there's a bit of commentary
    there about the trade-offs this implies.
    
    We've found that this does not inhibit the ability to accurately
    benchmark as measurements with LTO disabled are generally consistent
    with the results with LTO enabled and it makes it much faster (~2x) to
    rebuild after making a change.
    
    This is motivated by my interest in improving Codex TUI performance,
    which is blocked by the tragically builds right now.
    
    I tested incremental build times by making a no-op change to the
    `codex-cli` crate.
  • Use descriptive names for Cargo profile options (#21582)
    These are equivalent and their intent is clearer, e.g., I was confused
    if `debug = 1` meant the same thing as `debug = true` (it does not).
  • Grant sandbox users access to desktop runtime bin (#21564)
    ## Why
    
    Codex desktop copies bundled Windows binaries out of `WindowsApps` into
    a LocalAppData runtime cache before launching `codex.exe`. Sandboxed
    commands can then need to execute helpers from that cache, but the
    sandbox user group may not have read/execute access to the runtime bin
    directory.
    
    This makes the Windows sandbox refresh path repair that access directly
    so the packaged desktop runtime remains usable from sandboxed sessions.
    
    ## What changed
    
    - Added `setup_runtime_bin` to locate `%LOCALAPPDATA%\OpenAI\Codex\bin`,
    matching the desktop bundled-binaries destination path, with the same
    `USERPROFILE\AppData\Local` fallback shape.
    - During refresh setup, check whether `CodexSandboxUsers` already has
    read/execute access to the runtime bin directory.
    - If access is missing, grant `CodexSandboxUsers` `OI/CI/RX` inheritance
    on that directory.
    - If the runtime bin directory does not exist, no-op cleanly.
    
    ## Verification
    
    - `cargo build -p codex-windows-sandbox --bin
    codex-windows-sandbox-setup`
    - `cargo test -p codex-windows-sandbox --bin
    codex-windows-sandbox-setup`
    - Manual Windows ACL exercise against the installed packaged runtime
    bin:
    - existing inherited `CodexSandboxUsers:(I)(OI)(CI)(RX)` no-ops without
    changing SDDL
    - after disabling inheritance and removing the group ACE, setup adds
    `CodexSandboxUsers:(OI)(CI)(RX)`
    - with `LOCALAPPDATA` pointed at a fake location without
    `OpenAI\Codex\bin`, setup exits successfully and does not create the
    directory
    - restored the real runtime bin with inherited ACLs and confirmed the
    final SDDL matched the baseline exactly
  • Route ThreadManager rollout path reads through thread store (#21265)
    - Route ThreadManager rollout-path resume/fork through ThreadStore
    history reads.
    - Add in-memory store coverage proving path-addressed reads are used.
    
    This isn't strictly necessary for the ThreadStore migration, since these
    ThreadManager methods _only_ work for path-based lookups, but I'm trying
    to migrate all the rollout recorder callsites to use the threadstore
    were possible for consistency.
  • [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`.
  • [codex] Move tool specs onto handlers (#21461)
    ## Why
    
    This is the next stacked step after deleting the tool-handler kind
    indirection. Specs should come from the registered handlers themselves
    so registry construction has a single source of truth for handler
    behavior and exposed tool definitions.
    
    ## What changed
    
    - Added `ToolHandler::spec()` plus handler-provided parallel/code-mode
    metadata, and made `ToolRegistryBuilder::register_handler` automatically
    collect specs from registered handlers.
    - Moved builtin tool spec construction into the corresponding handlers
    and their adjacent `_spec` modules, including shell, unified exec, apply
    patch, view image, request plugin install, tool search, MCP resource,
    goals, planning, permissions, agent jobs, and multi-agent tools.
    - Reworked configurable handlers to receive their tool-building options
    through constructors, with non-optional handler options where the
    handler is always spec-backed. Shell fallback handlers keep an explicit
    no-spec mode because they are also registered as hidden dispatch
    aliases.
    - Kept `CodeModeExecuteHandler` on the explicit configured wrapper so
    the code-mode exec spec can still be built from the nested registry.
    
    ## Verification
    
    - `cargo check -p codex-core`
    - `cargo test -p codex-core tools::spec_plan::tests`
    - `cargo test -p codex-core tools::spec::tests`
    - `cargo test -p codex-core tools::handlers::multi_agents_spec::tests`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-core
    tools::handlers::multi_agents::tests`
    - `cargo test -p codex-core tools::handlers::apply_patch::tests`
    - `cargo test -p codex-core tools::handlers::unified_exec::tests`
    - `just fix -p codex-core`
    - `git diff --check`
  • 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>
  • add top-level remote-control command (#21424)
    ## Summary
    
    `codex --enable remote_control app-server --listen off` is the current
    way to start a headless, remote-controllable app-server, but it is hard
    to remember and exposes implementation details.
    
    This adds `codex remote-control` as a friendly top-level wrapper for
    that flow. The command starts a foreground app-server with local
    transports disabled and enables `remote_control` only for that
    invocation.
    
    ## Changes
    
    - Add a visible `codex remote-control` CLI subcommand.
    - Launch app-server with `AppServerTransport::Off`.
    - Append `features.remote_control=true` after root feature toggles so
    the explicit command wins over `--disable remote_control`.
    - Reject root `--remote` / `--remote-auth-token-env`, matching other
    non-TUI subcommands.
    - Add tests for parsing, launch defaults, override ordering, and remote
    flag rejection.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - `just fix -p codex-cli`
  • [codex] Remove string-keyed MCP tool maps (#21454)
    ## Summary
    
    This PR removes the synthetic `HashMap<String, ToolInfo>` keys from MCP
    tool discovery. `McpConnectionManager::list_all_tools()` now returns
    normalized `Vec<ToolInfo>`, and downstream code derives identity from
    `ToolInfo::canonical_tool_name()`.
    
    The motivation is to keep model-visible tool identity on
    `ToolName`/`ToolInfo` instead of parallel string map keys, so future
    namespace changes do not have to preserve otherwise-unused lookup keys.
    
    ## Changes
    
    - Rename the MCP normalization path from `qualify_tools` to
    `normalize_tools_for_model` and return tool values directly.
    - Flow MCP tool lists through connectors, plugin injection, router/spec
    building, code mode, and tool search as vectors/slices.
    - Keep direct/deferred subtraction local to `mcp_tool_exposure`, using
    `ToolName` values.
    - Update tests to compare `ToolName` instances where MCP identity
    matters.
    
    ## Validation
    
    - `cargo test -p codex-mcp test_normalize_tools`
    - `cargo test -p codex-core mcp_tool_exposure`
    - `cargo test -p codex-core
    direct_mcp_tools_register_namespaced_handlers`
    - `cargo test -p codex-core
    search_tool_registers_namespaced_mcp_tool_aliases`
    - `just fix -p codex-mcp`
    - `just fix -p codex-core`
  • feat: Expose plugin share metadata in shareContext (#21495)
    Extends PluginSummary.shareContext with shareUrl and reader shareTargets
  • [codex-analytics] add tool review event schema (#18747)
    ## Why
    
    We want to emit terminal review analytics for tool-related approval
    flows, but the event contract needs to exist before the reducer can
    publish anything.
    
    This PR is the schema-only slice for the Codex review event family.
    
    ## What changed
    
    - add the `ReviewEvent` analytics envelope in
    `codex-rs/analytics/src/events.rs`
    - define the review subject kind, reviewer, trigger, terminal status,
    and post-review resolution enums
    - define the review event payload with thread, turn, item, lineage,
    tool, and timing fields that the emitter stack will populate
    
    ## Verification
    
    - stacked verification in dependent PRs: `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/18747).
    * #18748
    * #21434
    * __->__ #18747
    * #17090
    * #17089
    * #20514
  • fix: preserve exact turn diffs after partial apply_patch failures (#21518)
    ## Why
    
    Follow-up to #21180: turn diffs are operation-backed now, but a failed
    `apply_patch` can still leave exact filesystem mutations behind. For
    example, a move can write the destination file before failing to remove
    the source. Treating the whole call as unknowable then drops a change
    that Codex actually knows happened, so the emitted turn diff can drift
    from the workspace.
    
    ## What changed
    
    -
    [`apply-patch`](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/apply-patch/src/lib.rs#L248-L345)
    now returns `ApplyPatchFailure` with the exact committed prefix
    accumulated before an error. If a write failure may already have mutated
    the target, the delta is marked inexact instead of being reused blindly.
    - Move handling now records the destination write before attempting
    source removal, so a partially failed move can still report the
    destination file that definitely landed
    ([code](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/apply-patch/src/lib.rs#L463-L521)).
    -
    [`ApplyPatchRuntime`](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/core/src/tools/runtimes/apply_patch.rs#L49-L67)
    now accumulates committed deltas across attempts and forwards them even
    when the visible tool result is failed or sandbox-denied ([runtime
    path](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/core/src/tools/runtimes/apply_patch.rs#L223-L250),
    [event
    path](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/core/src/tools/events.rs#L215-L225)).
    - `TurnDiffTracker` now consumes committed exact deltas rather than only
    fully successful patches; exact-empty failures leave the aggregate
    unchanged, while inexact deltas still invalidate it.
    
    ## Verification
    
    - Added a regression test covering a failed move that still emits the
    committed destination diff:
    [`apply_patch_failed_move_preserves_committed_destination_diff`](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/core/tests/suite/apply_patch_cli.rs#L1517-L1586).
    - Kept explicit coverage that an inexact delta clears the aggregate
    instead of publishing a guessed diff:
    [`apply_patch_clears_aggregated_diff_after_inexact_delta`](https://github.com/openai/codex/blob/f55724e0276a9b3213170daf2701ccfa0ce22646/codex-rs/core/tests/suite/apply_patch_cli.rs#L1589-L1655).
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [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`
  • Make turn diff tracking operation backed (#21180)
    ## Summary
    - replace filesystem-based turn diff tracking with an operation-backed
    accumulator
    - preserve enough verified apply_patch state to render move-overwrite
    cases correctly
    - keep the turn/diff/updated contract intact while removing remote-only
    turn-diff test skips
    
    This takes the assumption that no 3P services rely on the output format
    of `apply_patch`
    
    ## Why
    For the CCA file system isolation push
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: make built-in MCPs first-class runtime servers (#21356)
    ## DISCLAIMER
    This is experimental and no production service must rely on this
    
    ## Why
    
    Built-in MCPs are product-owned runtime capabilities, but they were
    previously flattened into the same config-backed stdio path as
    user-configured servers. That made them depend on a hidden `codex
    builtin-mcp` re-exec path, exposed them through config-oriented CLI
    flows, and erased distinctions the runtime needs to preserve—most
    notably whether an MCP call should count as external context for
    memory-mode pollution.
    
    ## What changed
    
    - Model product-owned built-ins separately from config-backed MCP
    servers via `BuiltinMcpServer` and `EffectiveMcpServer`.
    - Launch built-ins in process through a reusable async transport instead
    of the hidden `builtin-mcp` stdio subcommand.
    - Keep config-oriented CLI operations such as `codex mcp
    list/get/login/logout` scoped to configured servers, while merging
    built-ins only into the effective runtime server set.
    - Retain server metadata after launch so parallel-tool support and
    context classification come from the live server set; built-in
    `memories` is now classified as local Codex state rather than external
    context.
    
    ## Test plan
    
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core --test suite
    builtin_memories_mcp_call_does_not_mark_thread_memory_mode_polluted_when_configured`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • 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"
    />
  • [codex] fix PluginListParams test initializer (#21494)
    ## Summary
    - update the app-server protocol test fixture to include the required
    `marketplace_kinds` field on `PluginListParams`
    
    ## Why
    `PluginListParams` now requires `marketplace_kinds`, but a later-added
    test fixture in `common.rs` still constructed the older shape with only
    `cwds`. That stale initializer breaks the main build with `missing field
    marketplace_kinds`.
    
    ## Impact
    This is a test-only repair. It restores compilation without changing the
    JSON-RPC schema or runtime behavior.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
  • 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`
  • [codex] Add OpenAI Developers to tool suggest allowlist (#21423)
    ## Summary
    
    Add `openai-developers@openai-curated` to
    `TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST` so the OpenAI Developers
    plugin can be surfaced through tool suggestions once it is available in
    the Built by OpenAI marketplace.
    
    Update the discoverable plugin test fixture to assert the plugin is
    returned from the curated marketplace allowlist path.
    
    ## Validation
    
    - `cargo fmt --check` passed; rustfmt emitted the existing
    stable-channel warnings about `imports_granularity`.
    - `cargo test -p codex-core
    list_tool_suggest_discoverable_plugins_returns_uninstalled_curated_plugins`
    passed.
  • [codex] Delete tool handler plan indirection (#21427)
    ## Why
    
    The spec split in the parent PR still left an intermediate registry plan
    that recorded `ToolHandlerKind` values and translated them into concrete
    handlers later. That kept tool registration dependent on static enum
    bookkeeping instead of registering handlers from the same code that
    assembles their specs.
    
    ## What Changed
    
    - Make `build_tool_registry_builder` register concrete handlers directly
    while adding specs.
    - Add small `ToolRegistryBuilder` helpers for spec augmentation and
    nested code-mode inspection.
    - Remove `ToolHandlerKind`, `ToolHandlerSpec`, and `ToolRegistryPlan`.
    - Update spec-plan tests to assert against the built `ToolRegistry`
    instead of static handler descriptors.
    
    ## Validation
    
    - `cargo check -p codex-core`
    - `cargo test -p codex-core tools::spec_plan::tests`
    - `cargo test -p codex-core tools::spec::tests`
    - `just fix -p codex-core`
  • fix(tui): clear first inline viewport render (#21450)
    ## Why
    
    The alpha TUI can render the initial trust-directory prompt with stale
    terminal text showing through spaces when startup begins below existing
    shell output. The first inline viewport transition can happen while the
    previous viewport is still empty, so the old clear path no-ops before
    Ratatui draws the prompt. Ratatui then skips blank cells because its
    previous buffer also thinks those cells are blank, leaving old terminal
    contents visible inside the prompt.
    
    ## What Changed
    
    - Clear from the new inline viewport top when the previous viewport is
    empty during a viewport transition.
    - Keep the existing clear-from-old-viewport behavior for normal viewport
    updates.
    - Add a VT100-backed regression test that pre-fills terminal contents,
    performs the first viewport clear, and verifies stale text inside the
    new viewport is removed while shell content above the viewport remains.
    
    ## How to Test
    
    1. Start Codex alpha in a terminal that already has visible shell output
    above the cursor.
    2. Use a fresh untrusted project directory so the trust-directory prompt
    appears.
    3. Confirm the prompt text renders cleanly, with spaces staying blank
    instead of showing fragments of previous shell output.
    4. As a regression check, confirm content above the inline viewport is
    still preserved in terminal scrollback.
    
    Targeted tests:
    - `cargo test -p codex-tui
    first_viewport_change_clears_from_new_viewport_when_old_viewport_is_empty
    -- --nocapture`
    - `cargo test -p codex-tui`
  • 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.
  • [codex] Move tool specs into core handlers (#21416)
    ## Why
    
    This is the first mechanical slice of moving tool spec ownership toward
    the handlers. `codex-tools` should keep shared primitives and conversion
    helpers, while builtin tool specs and registration planning live in
    `codex-core` with the handlers that own those tools.
    
    Keeping this PR to relocation and import updates isolates the copy/move
    review from the later logic change that wires specs through registered
    handlers.
    
    ## What changed
    
    - Moved builtin tool spec constructors from `codex-rs/tools/src` into
    `codex-rs/core/src/tools/handlers/*_spec.rs` or nearby core tool
    modules.
    - Moved the registry planning code into
    `codex-rs/core/src/tools/spec_plan.rs` and its associated types/tests
    into core.
    - Kept shared primitives in `codex-tools`, including `ToolSpec`,
    schema/types, discovery/config primitives, dynamic/MCP conversion
    helpers, and code-mode collection helpers.
    - Updated handlers that referenced moved argument types or tool-name
    constants to use the core spec modules.
    - Moved spec tests next to the moved spec modules.
    
    ## Verification
    
    - `cargo check -p codex-tools`
    - `cargo check -p codex-core`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core _spec::tests`
    - `cargo test -p codex-core tools::spec_plan::tests`
    - `just fix -p codex-tools`
    - `just fix -p codex-core`
    
    Note: I also tried the broader `cargo test -p codex-core tools::`; it
    reached the moved spec-plan/spec tests successfully, then aborted with a
    stack overflow in
    `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`,
    which is outside this spec relocation.
  • 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`