Commit Graph

167 Commits

  • Add hooks/list app-server RPC (#19778)
    ## Why
    
    We need a way to list the available hooks to expose via the TUI and App
    so users can view and manage their hooks
    
    ## What
    
    - Adds `hooks/list` for one or more `cwd` values that returns discovered
    hook metadata
    
    ## Stack
    
    1. openai/codex#19705
    2. This PR - openai/codex#19778
    3. openai/codex#19840
    4. openai/codex#19882
    
    ## Review Notes
    
    The generated schema files account for most of the raw diff, these files
    have the core change:
    
    - `hooks/src/engine/discovery.rs` builds the inventory entries during
    hook discovery while leaving runtime handlers focused on execution.
    - `app-server/src/codex_message_processor.rs` wires `hooks/list` into
    the app-server flow for each requested `cwd`.
    - `app-server-protocol/src/protocol/v2.rs` defines the new v2
    request/response payloads exposed on the wire.
    
    ### Core Changes
    
    `core/src/plugins/manager.rs` adds `plugins_for_layer_stack(...)` so
    `skills/list` and `hooks/list`can resolve plugin state for each
    requested `cwd`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Update Codex login success page UX (#20136)
    ## Summary
    
    update the local login success page to match the Codex desktop auth UX
    use theme-aware colors and an inline 20px Codex mark
    keep the actual localhost success page aligned with the browser auth UX
    PR
    
    ## Tests
    
    <img width="1728" height="1117" alt="Screenshot 2026-04-29 at 12 00
    34 PM"
    src="https://github.com/user-attachments/assets/76a40c3f-07c3-452c-97da-e7c43717cd2c"
    />
  • [app-server] type client response payloads (#20050)
    ## Why
    
    `pr17088` adds typed server-originated request/response plumbing, but
    successful client responses are still erased into bare JSON-RPC `result`
    values before app-server can make any typed decision about them.
    
    This precursor PR keeps successful client responses typed until the
    outgoing response seam. It is intentionally limited to
    protocol/app-server plumbing so the analytics behavior change can review
    separately on top.
    
    ## What changed
    
    - Add `ClientResponsePayload` as the pre-serialization client response
    body type.
    - Route app-server successful response paths through the typed payload
    seam while preserving existing handler-local analytics behavior.
    - Keep `InterruptConversation` JSON-RPC-only because it has no
    `ClientResponse` variant.
    - Move the new payload conversion tests into a dedicated protocol test
    module.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server-protocol`
  • [codex-analytics] ingest server requests and responses (#17088)
    ## Why
    
    Codex analytics needs a typed seam for app-server-originated
    request/response traffic so future tool-approval analytics can consume
    those facts without adding bespoke callsite tracking each time. Server
    responses arrive as JSON-RPC `id + result` payloads, so analytics has to
    reconstruct the matching typed response from the original typed request
    while that request context still exists in app-server.
    
    This also puts analytics on the app-server outbound path, which needs to
    avoid keeping the runtime alive during shutdown. The final ownership fix
    keeps the normal strong auth-manager retention in analytics and makes
    the external-auth refresh bridge hold a weak back-reference to
    `OutgoingMessageSender`, breaking the runtime cycle at the bridge
    boundary instead of exposing retention policy through the analytics
    client API.
    
    ## What changed
    
    - Adds typed `ServerRequest` and `ServerResponse` analytics facts, plus
    `AnalyticsEventsClient::track_server_request` and
    `track_server_response`.
    - Renames the existing client-side facts to `ClientRequest` and
    `ClientResponse` so reducers can distinguish client-to-server traffic
    from server-to-client traffic.
    - Adds `ServerRequest::response_from_result`, allowing a stored typed
    request to decode the matching typed server response from a raw JSON-RPC
    result payload.
    - Threads `AnalyticsEventsClient` through `OutgoingMessageSender` and
    records targeted server requests, replayed targeted requests, and
    matching targeted responses with the responding connection id needed for
    correlation.
    - Intentionally leaves broadcast server requests/responses out of
    analytics for now because the current model is per connection, while
    broadcasts fan one logical request out across multiple connections.
    - Breaks the app-server shutdown cycle by storing
    `Weak<OutgoingMessageSender>` in `ExternalAuthRefreshBridge` and
    upgrading it only when an external-auth refresh is actually requested.
    - Keeps reducer ingestion of the new server-side facts as no-ops for
    now; this PR is plumbing for later tool-approval analytics work.
    
    ## Verification
    
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-app-server outgoing_message::tests::`
    - Covers typed-response reconstruction plus the targeted, replayed,
    broadcast-exclusion, and response-attribution analytics paths.
    
    ## Follow-up
    
    This PR intentionally stops at ingestion plumbing, so `ServerRequest`
    and `ServerResponse` facts are still reducer no-ops. Once a follow-up PR
    adds real downstream analytics output for those facts:
    
    - replace the temporary pre-reducer observation seam with reducer tests
    for the emitted event shape;
    - add end-to-end coverage in `app-server/tests/suite/v2/analytics.rs`
    for the real app-server workflow and captured analytics payload;
    - remove the temporary sender-level observer tests added here in favor
    of the real-output coverage above.
    
    ---
    
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17088).
    * #18748
    * #18747
    * #17090
    * #17089
    * #20241
    * #20239
    * __->__ #17088
  • feat: expose provider capability bounds to app server clients (#20049)
    follow up of #19442. The app server now exposes provider-derived bounds
    through a new v2 `modelProvider/read` method. The response reports the
    configured provider map key as `modelProvider` and returns the effective
    capability booleans so clients can align their UI with the same
    provider-owned limits used by core.
  • app-server: notify clients of remote-control status changes (#19919)
    ## Why
    
    Remote-control app-server enrollments have both an internal server id
    and the environment id exposed to remote-control clients. App-server
    clients need one current status snapshot that says whether remote
    control is usable and which environment id, if any, is exposed.
    
    A temporary websocket disconnect is not itself an identity change.
    Account changes, stale enrollment invalidation, successful
    re-enrollment, and missing ChatGPT auth are meaningful status changes.
    Disabled remote control remains `disabled` regardless of auth or SQLite
    state. SQLite startup failure disablement and enrollment persistence
    failures are handled in #20068; this PR reports the resulting effective
    status to clients.
    
    ## What changed
    
    - Adds v2 `remoteControl/status/changed` carrying `state` and
    `environmentId`.
    - Adds `RemoteControlConnectionState` values: `disabled`, `connecting`,
    `connected`, and `errored`.
    - Exposes remote-control status updates through `RemoteControlHandle`
    using a Tokio watch channel.
    - Always sends the current remote-control status snapshot to newly
    initialized app-server clients.
    - Broadcasts status changes to initialized app-server clients when state
    or environment id changes.
    - Treats missing ChatGPT auth as an `errored` status while leaving it
    retryable because auth can change at runtime.
    - Clears `environmentId` when enrollment is cleared for account changes,
    auth loss, stale backend invalidation, or disabled remote control.
    - Updates app-server protocol schema fixtures, generated TypeScript,
    app-server README, remote-control tests, and TUI exhaustive notification
    matches.
    
    ## Stack
    
    - Builds on #20068.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server transport::remote_control --lib`
    - `cargo check -p codex-tui`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
  • app-server: run initialized rpcs with keyed serialization (#17373)
    ## Why
    
    Initialized app-server RPCs no longer need to bottleneck behind one
    request processor path. Running them concurrently improves
    responsiveness, but several request families still mutate shared state
    or depend on ordered side effects. Those stateful families need an
    auditable serialization contract so concurrency does not reorder thread,
    config, auth, command, watcher, MCP, or similar state transitions.
    
    This PR keeps that boundary explicit: stateful work is serialized by the
    smallest useful key, while intentionally read-only or externally
    concurrent work remains unkeyed. In particular, `thread/list` and
    `thread/turns/list` explicitly have no serialization because they
    primarily read append-only rollout storage and should continue to be
    served concurrently.
    
    ## What changed
    
    - Adds `ClientRequest::serialization_scope()` in `app-server-protocol`
    and requires every client request definition to declare its
    serialization behavior.
    - Introduces keyed request scopes for thread, thread path, command exec
    process, fuzzy search session, fs watch, MCP OAuth, and global state
    buckets such as config, account auth, memory, and device keys.
    - Routes initialized app-server RPCs through per-key FIFO serialization
    while allowing unkeyed initialized requests to run concurrently.
    - Cancels in-flight initialized RPC work when the connection disconnects
    or the app-server exits so spawned request tasks do not outlive their
    session.
    - Adds focused coverage for representative keyed and unkeyed
    serialization scopes, including explicitly concurrent
    `thread/turns/list` behavior.
    
    ## Validation
    
    - Added protocol tests for representative keyed serialization scopes and
    intentionally unkeyed request families.
    - Added app-server request serialization tests covering per-key FIFO
    behavior, concurrent unkeyed execution, disconnect shutdown, and config
    read-after-write ordering.
    - Local focused protocol validation after the latest rebase is currently
    blocked by packageproxy failing to resolve locked `rustls-webpki
    0.103.13`; CI is expected to provide the full validation signal.
  • app-server-protocol: mark permission profiles experimental (#19899)
    ## Why
    
    `PermissionProfile` is now the canonical internal permissions
    representation, but the app-server wire shape is still intentionally
    unstable while the migration continues. Stable app-server clients should
    not see or generate code for these fields until the wire format settles.
    
    ## What changed
    
    - Marks every app-server v2 field that sends `PermissionProfile` as
    experimental, including `command/exec`, `thread/start`, `thread/resume`,
    `thread/fork`, and `turn/start` request/response payloads.
    - Enables per-field experimental inspection for `command/exec`, so
    `permissionProfile` is gated without making the entire method
    experimental.
    - Fixes the generated TypeScript schema filter to be comment-aware. The
    previous scanner treated apostrophes inside doc comments as string
    delimiters, so some experimental fields leaked into stable TypeScript
    even though stable JSON was filtered correctly.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19899).
    * #19900
    * __->__ #19899
  • Add goal app-server API (2 / 5) (#18074)
    Adds the app-server v2 goal API on top of the persisted goal state from
    PR 1.
    
    ## Why
    
    Clients need a stable app-server surface for reading and controlling
    materialized thread goals before the model tools and TUI can use them.
    Goal changes also need to be observable by app-server clients, including
    clients that resume an existing thread.
    
    ## What changed
    
    - Added v2 `thread/goal/get`, `thread/goal/set`, and `thread/goal/clear`
    RPCs for materialized threads.
    - Added `thread/goal/updated` and `thread/goal/cleared` notifications so
    clients can keep local goal state in sync.
    - Added resume/snapshot wiring so reconnecting clients see the current
    goal state for a thread.
    - Added app-server handlers that reconcile persisted rollout state
    before direct goal mutations.
    - Updated the app-server README plus generated JSON and TypeScript
    schema fixtures for the new API surface.
    
    ## Verification
    
    - Added app-server v2 coverage for goal get/set/clear behavior,
    notification emission, resume snapshots, and non-local thread-store
    interactions.
  • permissions: make legacy profile conversion cwd-free (#19414)
    ## Why
    
    The profile conversion path still required a `cwd` even when it was only
    translating a legacy `SandboxPolicy` into a `PermissionProfile`. That
    made profile producers invent an ambient `cwd`, which is exactly the
    anchoring we are trying to remove from permission-profile data. A legacy
    workspace-write policy can be represented symbolically instead: `:cwd =
    write` plus read-only `:project_roots` metadata subpaths.
    
    This PR creates that cwd-free base so the rest of the stack can stop
    threading cwd through profile construction. Callers that actually need a
    concrete runtime filesystem policy for a specific cwd still have an
    explicitly named cwd-bound conversion.
    
    ## What Changed
    
    - `PermissionProfile::from_legacy_sandbox_policy` now takes only
    `&SandboxPolicy`.
    - `FileSystemSandboxPolicy::from_legacy_sandbox_policy` is now the
    symbolic, cwd-free projection for profiles.
    - The old concrete projection is retained as
    `FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd` for
    runtime/boundary code that must materialize legacy cwd behavior.
    - Workspace-write profiles preserve `CurrentWorkingDirectory` and
    `ProjectRoots` special entries instead of materializing cwd into
    absolute paths.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-exec -p
    codex-exec-server -p codex-tui -p codex-sandboxing -p
    codex-linux-sandbox -p codex-analytics --tests`
    - `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-exec -p codex-exec-server -p codex-tui -p
    codex-sandboxing -p codex-linux-sandbox -p codex-analytics`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19414).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19391
    * __->__ #19414
  • permissions: make profiles represent enforcement (#19231)
    ## Why
    
    `PermissionProfile` is becoming the canonical permissions abstraction,
    but the old shape only carried optional filesystem and network fields.
    It could describe allowed access, but not who is responsible for
    enforcing it. That made `DangerFullAccess` and `ExternalSandbox` lossy
    when profiles were exported, cached, or round-tripped through app-server
    APIs.
    
    The important model change is that active permissions are now a disjoint
    union over the enforcement mode. Conceptually:
    
    ```rust
    pub enum PermissionProfile {
        Managed {
            file_system: FileSystemSandboxPolicy,
            network: NetworkSandboxPolicy,
        },
        Disabled,
        External {
            network: NetworkSandboxPolicy,
        },
    }
    ```
    
    This distinction matters because `Disabled` means Codex should apply no
    outer sandbox at all, while `External` means filesystem isolation is
    owned by an outside caller. Those are not equivalent to a broad managed
    sandbox. For example, macOS cannot nest Seatbelt inside Seatbelt, so an
    inner sandbox may require the outer Codex layer to use no sandbox rather
    than a permissive one.
    
    ## How Existing Modeling Maps
    
    Legacy `SandboxPolicy` remains a boundary projection, but it now maps
    into the higher-fidelity profile model:
    
    - `ReadOnly` and `WorkspaceWrite` map to `PermissionProfile::Managed`
    with restricted filesystem entries plus the corresponding network
    policy.
    - `DangerFullAccess` maps to `PermissionProfile::Disabled`, preserving
    the “no outer sandbox” intent instead of treating it as a lax managed
    sandbox.
    - `ExternalSandbox { network_access }` maps to
    `PermissionProfile::External { network }`, preserving external
    filesystem enforcement while still carrying the active network policy.
    - Split runtime policies that legacy `SandboxPolicy` cannot faithfully
    express, such as managed unrestricted filesystem plus restricted
    network, stay `Managed` instead of being collapsed into
    `ExternalSandbox`.
    - Per-command/session/turn grants remain partial overlays via
    `AdditionalPermissionProfile`; full `PermissionProfile` is reserved for
    complete active runtime permissions.
    
    ## What Changed
    
    - Change active `PermissionProfile` into a tagged union: `managed`,
    `disabled`, and `external`.
    - Keep partial permission grants separate with
    `AdditionalPermissionProfile` for command/session/turn overlays.
    - Represent managed filesystem permissions as either `restricted`
    entries or `unrestricted`; `glob_scan_max_depth` is non-zero when
    present.
    - Preserve old rollout compatibility by accepting the pre-tagged `{
    network, file_system }` profile shape during deserialization.
    - Preserve fidelity for important edge cases: `DangerFullAccess`
    round-trips as `disabled`, `ExternalSandbox` round-trips as `external`,
    and managed unrestricted filesystem + restricted network stays managed
    instead of being mistaken for external enforcement.
    - Preserve configured deny-read entries and bounded glob scan depth when
    full profiles are projected back into runtime policies, including
    unrestricted replacements that now become `:root = write` plus deny
    entries.
    - Regenerate the experimental app-server v2 JSON/TypeScript schema and
    update the `command/exec` README example for the tagged
    `permissionProfile` shape.
    
    ## Compatibility
    
    Legacy `SandboxPolicy` remains available at config/API boundaries as the
    compatibility projection. Existing rollout lines with the old
    `PermissionProfile` shape continue to load. The app-server
    `permissionProfile` field is experimental, so its v2 wire shape is
    intentionally updated to match the higher-fidelity model.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo check --tests`
    - `cargo test -p codex-protocol permission_profile`
    - `cargo test -p codex-protocol
    preserving_deny_entries_keeps_unrestricted_policy_enforceable`
    - `cargo test -p codex-app-server-protocol
    permission_profile_file_system_permissions`
    - `cargo test -p codex-app-server-protocol serialize_client_response`
    - `cargo test -p codex-core
    session_configured_reports_permission_profile_for_external_sandbox`
    - `just fix`
    - `just fix -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
  • Add app-server marketplace upgrade RPC (#19074)
    ## Summary
    - add a v2 `marketplace/upgrade` app-server RPC that mirrors the
    existing configured Git marketplace upgrade path
    - expose typed request/response/error payloads and regenerate
    JSON/TypeScript schema fixtures
    - add app-server integration coverage for all, named, already
    up-to-date, and invalid marketplace upgrade requests
    
    ## Tests
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server marketplace_upgrade`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fmt`
  • Add safety check notification and error handling (#19055)
    Adds a new app-server notification that fires when a user account has
    been flagged for potential safety reasons.
  • feat(auto-review) short-circuit (#18890)
    ## Summary
    Short circuit the convo if auto-review hits too many denials
    
    ## Testing
    - [x] Added unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add plumbing to approve stored Auto-Review denials (#18955)
    ## Summary
    
    This adds the structural plumbing needed for an app-server client to
    approve a previously denied Guardian review and carry that approval
    context into the next model turn.
    
    This PR does not add the actual `/auto-review-denials` tool 
    
    ## What Changed
    
    - Added app-server v2 RPC `thread/approveGuardianDeniedAction`.
    - Added generated JSON schema and TypeScript fixtures for
    `ThreadApproveGuardianDeniedAction*`.
    - Added core `Op::ApproveGuardianDeniedAction`.
    - Added a core handler that validates the event is a denied Guardian
    assessment and injects a developer message containing the stored denial
    event JSON.
    - Queues the approval context for the next turn if there is no active
    turn yet.
    - Added the TUI app-server bridge so `Op::ApproveGuardianDeniedAction {
    event }` is routed to the app-server request.
    
    ## What This Does Not Do
    
    - Does not add `/auto-review-denials`.
    - Does not add chat widget recent-denial state.
    - Does not add popup/list UI.
    - Does not add a product-facing denial lookup/store.
    - Does not change where Guardian denials are originally emitted or
    persisted.
    
    ## Verification
    
    - `cargo test -p codex-tui thread_approve_guardian_denied_action`
  • app-server: expose thread permission profiles (#18278)
    ## Why
    
    The `PermissionProfile` migration needs app-server clients to see the
    same constrained permission model that core is using at runtime. Before
    this PR, thread lifecycle responses only exposed the legacy
    `SandboxPolicy` shape, so clients still had to infer active permissions
    from sandbox fields. That makes downstream resume, fork, and override
    flows harder to make `PermissionProfile`-first.
    
    External sandbox policies are intentionally excluded from this canonical
    view. External enforcement cannot be round-tripped as a
    `PermissionProfile`, and exposing a lossy root-write profile would let
    clients accidentally change sandbox semantics if they echo the profile
    back later.
    
    ## What changed
    
    - Adds the app-server v2 `PermissionProfile` wire shape, including
    filesystem permissions and glob scan depth metadata.
    - Adds `PermissionProfileNetworkPermissions` so the profile response
    does not expose active network state through the older
    additional-permissions naming.
    - Returns `permissionProfile` from thread start, resume, and fork
    responses when the active sandbox can be represented as a
    `PermissionProfile`.
    - Keeps legacy `sandbox` in those responses for compatibility and
    documents `permissionProfile` as canonical when present.
    - Makes lifecycle `permissionProfile` nullable and returns `null` for
    `ExternalSandbox` to avoid exposing a lossy profile.
    - Regenerates the app-server JSON schema and TypeScript fixtures.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_response_permission_profile_omits_external_sandbox --
    --nocapture`
    - `cargo check --tests -p codex-analytics -p codex-exec -p codex-tui`
    - `just fix -p codex-app-server-protocol -p codex-app-server -p
    codex-analytics -p codex-exec -p codex-tui`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18278).
    * #18279
    * __->__ #18278
  • feat: add explicit AgentIdentity auth mode (#18785)
    ## Summary
    
    This PR adds `CodexAuth::AgentIdentity` as an explicit auth mode.
    
    An AgentIdentity auth record is a standalone `auth.json` mode. When
    `AuthManager::auth().await` loads that mode, it registers one
    process-scoped task and stores it in runtime-only state on the auth
    value. Header creation stays synchronous after that because the task is
    initialized before callers receive the auth object.
    
    This PR also removes the old feature flag path. AgentIdentity is
    selected by explicit auth mode, not by a hidden flag or lazy mutation of
    ChatGPT auth records.
    
    Reference old stack: https://github.com/openai/codex/pull/17387/changes
    
    ## Design Decisions
    
    - AgentIdentity is a real auth enum variant because it can be the only
    credential in `auth.json`.
    - The process task is ephemeral runtime state. It is not serialized and
    is not stored in rollout/session data.
    - Account/user metadata needed by existing Codex backend checks lives on
    the AgentIdentity record for now.
    - `is_chatgpt_auth()` remains token-specific.
    - `uses_codex_backend()` is the broader predicate for ChatGPT-token auth
    and AgentIdentity auth.
    
    ## Stack
    
    1. https://github.com/openai/codex/pull/18757: full revert
    2. https://github.com/openai/codex/pull/18871: isolated Agent Identity
    crate
    3. This PR: explicit AgentIdentity auth mode and startup task allocation
    4. https://github.com/openai/codex/pull/18811: migrate Codex backend
    auth callsites through AuthProvider
    5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs
    and load `CODEX_AGENT_IDENTITY`
    
    ## Testing
    
    Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
  • app-server: define device key v2 protocol (#18428)
    ## Why
    
    Clients need a stable app-server protocol surface for enrolling a local
    device key, retrieving its public key, and producing a device-bound
    proof.
    
    The protocol reports `protectionClass` explicitly so clients can
    distinguish hardware-backed keys from an explicitly allowed OS-protected
    fallback. Signing uses a tagged `DeviceKeySignPayload` enum rather than
    arbitrary bytes so each signed statement is auditable at the API
    boundary.
    
    ## What changed
    
    - Added v2 JSON-RPC methods for `device/key/create`,
    `device/key/public`, and `device/key/sign`.
    - Added request/response types for device-key metadata, SPKI public
    keys, protection classes, and ECDSA signatures.
    - Added `DeviceKeyProtectionPolicy` with hardware-only default behavior
    and an explicit `allow_os_protected_nonextractable` option.
    - Added the initial `remoteControlClientConnection` signing payload
    variant.
    - Regenerated JSON Schema and TypeScript fixtures for app-server
    clients.
    
    ## Stack
    
    This is PR 1 of 4 in the device-key app-server stack.
    
    ## Validation
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
  • protocol: preserve glob scan depth in permission profiles (#18713)
    ## Why
    
    #18274 made `PermissionProfile` the canonical file-system permissions
    shape, but the round-trip from `FileSystemSandboxPolicy` to
    `PermissionProfile` still dropped one piece of policy metadata:
    `glob_scan_max_depth`.
    
    That field is security-relevant for deny-read globs such as `**/*.env`.
    On Linux, bubblewrap sandbox construction uses it to bound unreadable
    glob expansion. If a profile copied from active runtime permissions
    loses this value and is submitted back as an override, the resulting
    `FileSystemSandboxPolicy` can behave differently even though the visible
    permission entries look equivalent.
    
    ## What changed
    
    - Add `glob_scan_max_depth` to protocol `FileSystemPermissions` and
    preserve it when converting to/from `FileSystemSandboxPolicy`.
    - Keep legacy `read`/`write` JSON for simple path-only permissions, but
    force canonical JSON when glob scan depth is present so the metadata is
    not silently dropped.
    - Carry `globScanMaxDepth` through app-server
    `AdditionalFileSystemPermissions`, generated JSON/TypeScript schemas,
    and app-server/TUI conversion call sites.
    - Preserve the metadata through sandboxing permission normalization,
    merging, and intersection.
    - Carry the merged scan depth into the effective
    `FileSystemSandboxPolicy` used for command execution, so bounded
    deny-read globs reach Linux bubblewrap materialization.
    
    ## Verification
    
    - `cargo test -p codex-sandboxing glob_scan -- --nocapture`
    - `cargo test -p codex-sandboxing policy_transforms -- --nocapture`
    - `just fix -p codex-sandboxing`
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18713).
    * #18288
    * #18287
    * #18286
    * #18285
    * #18284
    * #18283
    * #18282
    * #18281
    * #18280
    * #18279
    * #18278
    * #18277
    * #18276
    * #18275
    * __->__ #18713
  • Wire the PatchUpdated events through app_server (#18289)
    Wires patch_updated events through app_server. These events are parsed
    and streamed while apply_patch is being written by the model. Also adds 500ms of buffering to the patch_updated events in the diff_consumer.
    
    The eventual goal is to use this to display better progress indicators in
    the codex app.
  • protocol: canonicalize file system permissions (#18274)
    ## Why
    
    `PermissionProfile` needs stable, canonical file-system semantics before
    it can become the primary runtime permissions abstraction. Without a
    canonical form, callers have to keep re-deriving legacy sandbox maps and
    profile comparisons remain lossy or order-dependent.
    
    ## What changed
    
    This adds canonicalization helpers for `FileSystemPermissions` and
    `PermissionProfile`, expands special paths into explicit sandbox
    entries, and updates permission request/conversion paths to consume
    those canonical entries. It also tightens the legacy bridge so root-wide
    write profiles with narrower carveouts are not silently projected as
    full-disk legacy access.
    
    ## Verification
    
    - `cargo test -p codex-protocol
    root_write_with_read_only_child_is_not_full_disk_write -- --nocapture`
    - `cargo test -p codex-sandboxing permission -- --nocapture`
    - `cargo test -p codex-tui permissions -- --nocapture`
  • [codex] Add marketplace/remove app-server RPC (#17751)
    ## Summary
    
    Add a new app-server `marketplace/remove` RPC on top of the shared
    marketplace-remove implementation.
    
    This change:
    - adds `MarketplaceRemoveParams` / `MarketplaceRemoveResponse` to the
    app-server protocol
    - wires the new request through `codex_message_processor`
    - reuses the shared core marketplace-remove flow from the stacked
    refactor PR
    - updates generated schema files and adds focused app-server coverage
    
    ## Validation
    
    - `just write-app-server-schema`
    - `just fmt`
    - heavy compile/test coverage deferred to GitHub CI per request
  • [codex] Add owner nudge app-server API (#18220)
    ## Summary
    
    Second PR in the split from #17956. Stacked on #18227.
    
    - adds app-server v2 protocol/schema support for
    `account/sendAddCreditsNudgeEmail`
    - adds the backend-client `send_add_credits_nudge_email` request and
    request body mapping
    - handles the app-server request with auth checks, backend call, and
    cooldown mapping
    - adds the disabled `workspace_owner_usage_nudge` feature flag and
    focused app-server/backend tests
    
    ## Validation
    
    - `cargo test -p codex-backend-client`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server rate_limits`
    - `cargo test -p codex-tui workspace_`
    - `cargo test -p codex-tui status_`
    - `just fmt`
    - `just fix -p codex-backend-client`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
  • feat: Budget skill metadata and surface trimming as a warning (#18298)
    Cap the model-visible skills section to a small share of the context
    window, with a fallback character budget, and keep only as many implicit
    skills as fit within that budget.
    
    Emit a non-fatal warning when enabled skills are omitted, and add a new
    app-server warning notification
    
    Record thread-start skill metrics for total enabled skills, kept skills,
    and whether truncation happened
    
    ---------
    
    Co-authored-by: Matthew Zeng <mzeng@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • Add sorting/backwardsCursor to thread/list and new thread/turns/list api (#17305)
    To improve performance of UI loads from the app, add two main
    improvements:
    1. The `thread/list` api now gets a `sortDirection` request field and a
    `backwardsCursor` to the response, which lets you paginate forwards and
    backwards from a window. This lets you fetch the first few items to
    display immediately while you paginate to fill in history, then can
    paginate "backwards" on future loads to catch up with any changes since
    the last UI load without a full reload of the entire data set.
    2. Added a new `thread/turns/list` api which also has sortDirection and
    backwardsCursor for the same behavior as `thread/list`, allowing you the
    same small-fetch for immediate display followed by background fill-in
    and resync catchup.
  • Sync local plugin imports, async remote imports, refresh caches after… (#18246)
    … import
    
    ## Why
    
    `externalAgentConfig/import` used to spawn plugin imports in the
    background and return immediately. That meant local marketplace imports
    could still be in flight when the caller refreshed plugin state, so
    newly imported plugins would not show up right away.
    
    This change makes local marketplace imports complete before the RPC
    returns, while keeping remote marketplace imports asynchronous so we do
    not block on remote fetches.
    
    ## What changed
    
    - split plugin migration details into local and remote marketplace
    imports based on the external config source
    - import local marketplaces synchronously during
    `externalAgentConfig/import`
    - return pending remote plugin imports to the app-server so it can
    finish them in the background
    - clear the plugin and skills caches before responding to plugin
    imports, and again after background remote imports complete, so the next
    `plugin/list` reloads fresh state
    - keep marketplace source parsing encapsulated behind
    `is_local_marketplace_source(...)` instead of re-exporting the internal
    enum
    - add core and app-server coverage for the synchronous local import path
    and the pending remote import path
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core` (currently fails an existing unrelated
    test:
    `config_loader::tests::cli_override_can_update_project_local_mcp_server_when_project_is_trusted`)
    - `cargo test` (currently fails existing `codex-app-server` integration
    tests in MCP/skills/thread-start areas, plus the unrelated `codex-core`
    failure above)
  • [codex][mcp] Add resource uri meta to tool call item. (#17831)
    - [x] Add resource uri meta to tool call item so that the app-server
    client can start prefetching resources immediately without loading mcp
    server status.
  • Spread AbsolutePathBuf (#17792)
    Mechanical change to promote absolute paths through code.
  • Add realtime output modality and transcript events (#17701)
    - Add outputModality to thread/realtime/start and wire text/audio output
    selection through app-server, core, API, and TUI.\n- Rename the realtime
    transcript delta notification and add a separate transcript done
    notification that forwards final text from item done without correlating
    it with deltas.
  • [codex] Refactor marketplace add into shared core flow (#17717)
    ## Summary
    
    Move `codex marketplace add` onto a shared core implementation so the
    CLI and app-server path can use one source of truth.
    
    This change:
    - adds shared marketplace-add orchestration in `codex-core`
    - switches the CLI command to call that shared implementation
    - removes duplicated CLI-only marketplace add helpers
    - preserves focused parser and add-path coverage while moving the shared
    behavior into core tests
    
    ## Why
    
    The new `marketplace/add` RPC should reuse the same underlying
    marketplace-add flow as the CLI. This refactor lands that consolidation
    first so the follow-up app-server PR can be mostly protocol and handler
    wiring.
    
    ## Validation
    
    - `cargo test -p codex-core marketplace_add`
    - `cargo test -p codex-cli marketplace_cmd`
    - `just fix -p codex-core`
    - `just fix -p codex-cli`
    - `just fmt`
  • Add turn item injection API (#17703)
    ## Summary
    - Add `turn/inject_items` app-server v2 request support for appending
    raw Responses API items to a loaded thread history without starting a
    turn.
    - Generate JSON schema and TypeScript protocol artifacts for the new
    params and empty response.
    - Document the new endpoint and include a request/response example.
    - Preserve compatibility with the typo alias `turn/injet_items` while
    returning the canonical method name.
    
    ## Testing
    - Not run (not requested)
  • Expose instruction sources (AGENTS.md) via app server (#17506)
    Addresses #17498
    
    Problem: The TUI derived /status instruction source paths from the local
    client environment, which could show stale <none> output or incorrect
    paths when connected to a remote app server.
    
    Solution: Add an app-server v2 instructionSources snapshot to thread
    start/resume/fork responses, default it to an empty list when older
    servers omit it, and render TUI /status from that server-provided
    session data.
    
    Additional context: The app-server field is intentionally named
    instructionSources rather than AGENTS.md-specific terminology because
    the loaded instruction sources can include global instructions, project
    AGENTS.md files, AGENTS.override.md, user-defined instruction files, and
    future dynamic sources.
  • [mcp] Support MCP Apps part 3 - Add mcp tool call support. (#17364)
    - [x] Add a new app-server method so that MCP Apps can call their own
    MCP server directly.
  • Revert "Option to Notify Workspace Owner When Usage Limit is Reached" (#17391)
    Reverts openai/codex#16969
    
    #sev3-2026-04-10-accountscheckversion-500s-for-openai-workspace-7300
  • Option to Notify Workspace Owner When Usage Limit is Reached (#16969)
    ## Summary
    - Replace the manual `/notify-owner` flow with an inline confirmation
    prompt when a usage-based workspace member hits a credits-depleted
    limit.
    - Fetch the current workspace role from the live ChatGPT
    `accounts/check/v4-2023-04-27` endpoint so owner/member behavior matches
    the desktop and web clients.
    - Keep owner, member, and spend-cap messaging distinct so we only offer
    the owner nudge when the workspace is actually out of credits.
    
    ## What Changed
    - `backend-client`
    - Added a typed fetch for the current account role from
    `accounts/check`.
      - Mapped backend role values into a Rust workspace-role enum.
    - `app-server` and protocol
      - Added `workspaceRole` to `account/read` and `account/updated`.
    - Derived `isWorkspaceOwner` from the live role, with a fallback to the
    cached token claim when the role fetch is unavailable.
    - `tui`
      - Removed the explicit `/notify-owner` slash command.
    - When a member is blocked because the workspace is out of credits, the
    error now prompts:
    - `Your workspace is out of credits. Request more from your workspace
    owner? [y/N]`
      - Choosing `y` sends the existing owner-notification request.
    - Choosing `n`, pressing `Esc`, or accepting the default selection
    dismisses the prompt without sending anything.
    - Selection popups now honor explicit item shortcuts, which is how the
    `y` / `n` interaction is wired.
    
    ## Reviewer Notes
    - The main behavior change is scoped to usage-based workspace members
    whose workspace credits are depleted.
    - Spend-cap reached should not show the owner-notification prompt.
    - Owners and admins should continue to see `/usage` guidance instead of
    the member prompt.
    - The live role fetch is best-effort; if it fails, we fall back to the
    existing token-derived ownership signal.
    
    ## Testing
    - Manual verification
      - Workspace owner does not see the member prompt.
    - Workspace member with depleted credits sees the confirmation prompt
    and can send the nudge with `y`.
    - Workspace member with spend cap reached does not see the
    owner-notification prompt.
    
    ### Workspace member out of usage
    
    https://github.com/user-attachments/assets/341ac396-eff4-4a7f-bf0c-60660becbea1
    
    ### Workspace owner
    <img width="1728" height="1086" alt="Screenshot 2026-04-09 at 11 48
    22 AM"
    src="https://github.com/user-attachments/assets/06262a45-e3fc-4cc4-8326-1cbedad46ed6"
    />
  • Forward app-server turn clientMetadata to Responses (#16009)
    ## Summary
    App-server v2 already receives turn-scoped `clientMetadata`, but the
    Rust app-server was dropping it before the outbound Responses request.
    This change keeps the fix lightweight by threading that metadata through
    the existing turn-metadata path rather than inventing a new transport.
    
    ## What we're trying to do and why
    We want turn-scoped metadata from the app-server protocol layer,
    especially fields like Hermes/GAAS run IDs, to survive all the way to
    the actual Responses API request so it is visible in downstream
    websocket request logging and analytics.
    
    The specific bug was:
    - app-server protocol uses camelCase `clientMetadata`
    - Responses transport already has an existing turn metadata carrier:
    `x-codex-turn-metadata`
    - websocket transport already rewrites that header into
    `request.request_body.client_metadata["x-codex-turn-metadata"]`
    - but the Rust app-server never parsed or stored `clientMetadata`, so
    nothing from the app-server request was making it into that existing
    path
    
    This PR fixes that without adding a new header or a second metadata
    channel.
    
    ## How we did it
    ### Protocol surface
    - Add optional `clientMetadata` to v2 `TurnStartParams` and
    `TurnSteerParams`
    - Regenerate the JSON schema / TypeScript fixtures
    - Update app-server docs to describe the field and its behavior
    
    ### Runtime plumbing
    - Add a dedicated core op for app-server user input carrying turn-scoped
    metadata: `Op::UserInputWithClientMetadata`
    - Wire `turn/start` and `turn/steer` through that op / signature path
    instead of dropping the metadata at the message-processor boundary
    - Store the metadata in `TurnMetadataState`
    
    ### Transport behavior
    - Reuse the existing serialized `x-codex-turn-metadata` payload
    - Merge the new app-server `clientMetadata` into that JSON additively
    - Do **not** replace built-in reserved fields already present in the
    turn metadata payload
    - Keep websocket behavior unchanged at the outer shape level: it still
    sends only `client_metadata["x-codex-turn-metadata"]`, but that JSON
    string now contains the merged fields
    - Keep HTTP fallback behavior unchanged except that the existing
    `x-codex-turn-metadata` header now includes the merged fields too
    
    ### Request shape before / after
    Before, a websocket `response.create` looked like:
    ```json
    {
      "type": "response.create",
      "client_metadata": {
        "x-codex-turn-metadata": "{\"session_id\":\"...\",\"turn_id\":\"...\"}"
      }
    }
    ```
    Even if the app-server caller supplied `clientMetadata`, it was not
    represented there.
    
    After, the same request shape is preserved, but the serialized payload
    now includes the new turn-scoped fields:
    ```json
    {
      "type": "response.create",
      "client_metadata": {
        "x-codex-turn-metadata": "{\"session_id\":\"...\",\"turn_id\":\"...\",\"fiber_run_id\":\"fiber-start-123\",\"origin\":\"gaas\"}"
      }
    }
    ```
    
    ## Validation
    ### Targeted tests added / updated
    - protocol round-trip coverage for `clientMetadata` on `turn/start` and
    `turn/steer`
    - protocol round-trip coverage for `Op::UserInputWithClientMetadata`
    - `TurnMetadataState` merge test proving client metadata is added
    without overwriting reserved built-in fields
    - websocket request-shape test proving outbound `response.create`
    contains merged metadata inside
    `client_metadata["x-codex-turn-metadata"]`
    - app-server integration tests proving:
    - `turn/start` forwards `clientMetadata` into the outbound Responses
    request path
      - websocket warmup + real turn request both behave correctly
      - `turn/steer` updates the follow-up request metadata
    
    ### Commands run
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core
    turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields
    --lib`
    - `cargo test -p codex-core --test all
    responses_websocket_preserves_custom_turn_metadata_fields`
    - `cargo test -p codex-app-server --test all client_metadata`
    - `cargo test -p codex-app-server --test all
    turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2
    -- --nocapture`
    - `just fmt`
    - `just fix -p codex-core -p codex-protocol -p codex-app-server-protocol
    -p codex-app-server`
    - `just fix -p codex-exec -p codex-tui-app-server`
    - `just argument-comment-lint`
    
    ### Full suite note
    `cargo test` in `codex-rs` still fails in:
    -
    `suite::v2::turn_interrupt::turn_interrupt_resolves_pending_command_approval_request`
    
    I verified that same failure on a clean detached `HEAD` worktree with an
    isolated `CARGO_TARGET_DIR`, so it is not caused by this patch.
  • Add realtime voice selection (#17176)
    - Add realtime voice selection for realtime/start.
    - Expose the supported v1/v2 voice lists and cover explicit, configured,
    default, and invalid voice paths.
  • Move default realtime prompt into core (#17165)
    - Adds a core-owned realtime backend prompt template and preparation
    path.
    - Makes omitted realtime start prompts use the core default, while null
    or empty prompts intentionally send empty instructions.
    - Covers the core realtime path and app-server v2 path with integration
    coverage.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add WebRTC transport to realtime start (#16960)
    Adds WebRTC startup to the experimental app-server
    `thread/realtime/start` method with an optional transport enum. The
    websocket path remains the default; WebRTC offers create the realtime
    session through the shared start flow and emit the answer SDP via
    `thread/realtime/sdp`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [app-server-protocol] introduce generic ServerResponse for app-server-protocol (#17044)
    - introduces `ServerResponse` as the symmetrical typed response union to
    `ServerRequest` for app-server-protocol
    - enables scalable event stream ingestion for use cases such as
    analytics, particularly for tools/approvals
    - no runtime behavior changes, protocol/schema plumbing only
    - mirrors #15921
  • app-server: Move watch_id to request of fs/watch (#17026)
    It's easier for clients to maintain watchers if they define the watch
    id, so move it into the request.
    It's not used yet, so should be a safe change.
  • [mcp] Support MCP Apps part 1. (#16082)
    - [x] Add `mcpResource/read` method to read mcp resource.
  • Fix fork source display in /status (expose forked_from_id in app server) (#16596)
    Addresses #16560
    
    Problem: `/status` stopped showing the source thread id in forked TUI
    sessions after the app-server migration.
    
    Solution: Carry fork source ids through app-server v2 thread data and
    the TUI session adapter, and update TUI fixtures so `/status` matches
    the old TUI behavior.
  • Add ChatGPT device-code login to app server (#15525)
    ## Problem
    
    App-server clients could only initiate ChatGPT login through the browser
    callback flow, even though the shared login crate already supports
    device-code auth. That left VS Code, Codex App, and other app-server
    clients without a first-class way to use the existing device-code
    backend when browser redirects are brittle or when the client UX wants
    to own the login ceremony.
    
    ## Mental model
    
    This change adds a second ChatGPT login start path to app-server:
    clients can now call `account/login/start` with `type:
    "chatgptDeviceCode"`. App-server immediately returns a `loginId` plus
    the device-code UX payload (`verificationUrl` and `userCode`), then
    completes the login asynchronously in the background using the existing
    `codex_login` polling flow. Successful device-code login still resolves
    to ordinary `chatgpt` auth, and completion continues to flow through the
    existing `account/login/completed` and `account/updated` notifications.
    
    ## Non-goals
    
    This does not introduce a new auth mode, a new account shape, or a
    device-code eligibility discovery API. It also does not add automatic
    fallback to browser login in core; clients remain responsible for
    choosing when to request device code and whether to retry with a
    different UX if the backend/admin policy rejects it.
    
    ## Tradeoffs
    
    We intentionally keep `login_chatgpt_common` as a local validation
    helper instead of turning it into a capability probe. Device-code
    eligibility is checked by actually calling `request_device_code`, which
    means policy-disabled cases surface as an immediate request error rather
    than an async completion event. We also keep the active-login state
    machine minimal: browser and device-code logins share the same public
    cancel contract, but device-code cancellation is implemented with a
    local cancel token rather than a larger cross-crate refactor.
    
    ## Architecture
    
    The protocol grows a new `chatgptDeviceCode` request/response variant in
    app-server v2. On the server side, the new handler reuses the existing
    ChatGPT login precondition checks, calls `request_device_code`, returns
    the device-code payload, and then spawns a background task that waits on
    either cancellation or `complete_device_code_login`. On success, it
    reuses the existing auth reload and cloud-requirements refresh path
    before emitting `account/login/completed` success and `account/updated`.
    On failure or cancellation, it emits only `account/login/completed`
    failure. The existing `account/login/cancel { loginId }` contract
    remains unchanged and now works for both browser and device-code
    attempts.
    
    
    ## Tests
    
    Added protocol serialization coverage for the new request/response
    variant, plus app-server tests for device-code success, failure, cancel,
    and start-time rejection behavior. Existing browser ChatGPT login
    coverage remains in place to show that the callback-based flow is
    unchanged.
  • [app-server-protocol] introduce generic ClientResponse for app-server-protocol (#15921)
    - introduces `ClientResponse` as the symmetrical typed response union to
    `ClientRequest` for app-server-protocol
    - enables scalable event stream ingestion for use cases such as
    analytics
    - no runtime behavior changes, protocol/schema plumbing only
  • permissions: remove macOS seatbelt extension profiles (#15918)
    ## Why
    
    `PermissionProfile` should only describe the per-command permissions we
    still want to grant dynamically. Keeping
    `MacOsSeatbeltProfileExtensions` in that surface forced extra macOS-only
    approval, protocol, schema, and TUI branches for a capability we no
    longer want to expose.
    
    ## What changed
    
    - Removed the macOS-specific permission-profile types from
    `codex-protocol`, the app-server v2 API, and the generated
    schema/TypeScript artifacts.
    - Deleted the core and sandboxing plumbing that threaded
    `MacOsSeatbeltProfileExtensions` through execution requests and seatbelt
    construction.
    - Simplified macOS seatbelt generation so it always includes the fixed
    read-only preferences allowlist instead of carrying a configurable
    profile extension.
    - Removed the macOS additional-permissions UI/docs/test coverage and
    deleted the obsolete macOS permission modules.
    - Tightened `request_permissions` intersection handling so explicitly
    empty requested read lists are preserved only when that field was
    actually granted, avoiding zero-grant responses being stored as active
    permissions.
  • chore: remove skill metadata from command approval payloads (#15906)
    ## Why
    
    This is effectively a follow-up to
    [#15812](https://github.com/openai/codex/pull/15812). That change
    removed the special skill-script exec path, but `skill_metadata` was
    still being threaded through command-approval payloads even though the
    approval flow no longer uses it to render prompts or resolve decisions.
    
    Keeping it around added extra protocol, schema, and client surface area
    without changing behavior.
    
    Removing it keeps the command-approval contract smaller and avoids
    carrying a dead field through app-server, TUI, and MCP boundaries.
    
    ## What changed
    
    - removed `ExecApprovalRequestSkillMetadata` and the corresponding
    `skillMetadata` field from core approval events and the v2 app-server
    protocol
    - removed the generated JSON and TypeScript schema output for that field
    - updated app-server, MCP server, TUI, and TUI app-server approval
    plumbing to stop forwarding the field
    - cleaned up tests that previously constructed or asserted
    `skillMetadata`
    
    ## Testing
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-test-client`
    - `cargo test -p codex-mcp-server`
    - `just argument-comment-lint`
  • [app-server] Add a method to override feature flags. (#15601)
    - [x] Add a method to override feature flags globally and not just
    thread level.