2094 Commits

  • Update safety check links (#30491)
    ## Summary
    
    Bio/Cyber safety surfaces in the TUI could send users to stale Trusted
    Access pages, and safety buffering did not always expose the Help
    Center.
    
    This follow-up to #30317 adds the missing Learn more action, refreshes
    the Bio access URL and block copy, and updates the affected snapshots
    while preserving the existing retry and wait behavior.
  • [codex] Treat max as a first-class reasoning effort (#30467)
    ## Why
    
    The Bedrock GPT-5.6 catalog advertises `max`, but Codex treated it as an
    opaque custom effort. That made the reasoning picker render it as
    lowercase `max` while known efforts use productized labels.
    
    Making `max` a known effort aligns catalog data, parsing, and UI
    presentation without changing the `max` wire value or persisted
    representation.
    
    ## What changed
    
    - Add first-class `ReasoningEffort::Max` parsing and serialization.
    - Use the typed effort in the Bedrock catalog and render it as `Max` in
    the TUI.
    - Preserve forward-compatible custom-effort coverage with a genuinely
    unknown `future` value.
    
    ### Before
    <img width="559" height="124" alt="Screenshot 2026-06-28 at 12 08 47 PM"
    src="https://github.com/user-attachments/assets/7c43cf4f-020b-4605-9239-0a9c97eb7364"
    />
    
    ### After
    <img width="558" height="107" alt="Screenshot 2026-06-28 at 12 09 10 PM"
    src="https://github.com/user-attachments/assets/b9cc5ded-c940-43b4-b024-bba25abe0a17"
    />
  • fix(tui): clear completed safety buffering prompt (#30490)
    ## Why
    
    The safety-buffering prompt is a modal TUI view, but the normal
    successful-turn path only hid the running status indicator. If the turn
    completed while the prompt was open, the stale modal remained over the
    composer until the user dismissed it or another turn started.
    
    This aligns the TUI with the app behavior: keep the safety notice
    visible while the turn is active, then remove it when the turn becomes
    terminal. It also prevents the stale retry action from changing the
    model and reasoning effort for a future turn after the buffered turn has
    already completed.
    
    | New copy |
    |---|
    | <img width="1014" height="313" alt="CleanShot 2026-06-28 at 20 27 18"
    src="https://github.com/user-attachments/assets/f0f37359-5d77-442f-add2-9d1874bdc422"
    /> |
    
    ## What changed
    
    - Clear the active safety-buffering view and retry state when a turn
    completes successfully.
    - Update the retry-capable message to say “Hang tight or retry with a
    faster model”.
    - Extend the safety-buffering regression coverage to verify that the
    prompt remains visible after assistant output starts and disappears when
    the turn completes.
    - Update the TUI snapshot for the revised copy.
    
    This is a follow-up to #29919.
    
    ## How to Test
    
    1. Start a TUI turn that receives `model/safetyBuffering/updated` with
    `showBufferingUi: true` and a `fasterModel`.
    2. Confirm the prompt says “Hang tight or retry with a faster model”.
    3. Let the turn continue and confirm the prompt remains visible while
    the turn is active.
    4. Let the turn finish successfully and confirm the prompt disappears
    and the composer is restored without requiring an extra keypress.
    5. Confirm a buffering update without a faster model still shows the
    shorter non-retry message.
    
    Targeted automated coverage:
    
    - `just test -p codex-tui safety_buffering` — 4 passed.
    - `just test -p codex-tui` — 2,951 passed; two unrelated Guardian
    feature-flag tests failed identically on `main` in this environment.
    
    The argument-comment lint was also audited manually. The workspace Bazel
    invocation was blocked by a missing external LLVM `compiler-rt` BUILD
    file, and the packaged per-crate fallback uses a nightly older than the
    current `sqlx` minimum Rust version.
  • [codex] Enable remote plugins by default (#30297)
    ## Summary
    
    - enable the remote plugin feature by default
    - promote the remote plugin feature from under development to stable
    - preserve the existing `features.remote_plugin` override for explicitly
    disabling it
    - keep legacy disabled-path coverage explicit in TUI and app-server
    tests
    
    ## Impact
    
    Remote plugin functionality is enabled by default for configurations
    that do not set the feature flag. The existing Codex backend
    authentication gate still applies.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-features`
    - `just test -p codex-tui
    plugins_popup_remote_section_fallback_states_snapshot`
    - targeted `codex-app-server` plugin-list and skills-list tests
    - `git diff --check`
    
    The full TUI and app-server suites were also exercised locally. All
    remote-plugin-related coverage passed; unrelated local
    sandbox/test-binary failures remain outside this change.
  • [codex] Support npm marketplace plugin sources (#29375)
    ## Why
    
    Marketplace source deserialization treated `{"source":"npm", ...}` as
    unsupported. The loader logged and skipped the entry, so npm-backed
    plugins never appeared in `plugin list --available` and `plugin add`
    returned "plugin not found".
    
    Codex plugins are installed from a plugin root, not from an npm
    dependency tree. For npm-backed marketplace entries, Codex should fetch
    the published package contents without running package scripts or
    installing unrelated dependencies.
    
    ## What changed
    
    - Add `npm` marketplace plugin sources with `package`, optional semver
    `version` or version range, and optional HTTPS `registry`.
    - Reject unsafe npm source fields before materialization, including
    invalid package names, non-semver version selectors, plaintext or
    credential-bearing registry URLs, and registry query/fragment data.
    - Materialize npm plugins with `npm pack --ignore-scripts`, then unpack
    the resulting tarball through the existing hardened plugin bundle
    extractor.
    - Enforce npm archive and extracted-size limits, require the standard
    npm `package/` archive root, and verify the extracted `package.json`
    name matches the requested package before installing.
    - Keep plugin listings, install-source descriptions, CLI JSON/human
    output, app-server v2 `PluginSource`, TUI source summaries, regenerated
    schema fixtures, and app-server documentation in sync.
    
    ## Impact
    
    Marketplaces can distribute Codex plugins from public or configured
    private HTTPS npm registries using the same install flow as existing
    materialized plugin sources. `npm` must be available on `PATH` when an
    npm-backed plugin is installed.
    
    Fixes #27831
    
    ## Validation
    
    - `just write-app-server-schema`
    - `just test -p codex-core-plugins -p codex-app-server-protocol -p
    codex-app-server -p codex-cli`
      - npm/schema/core-plugin coverage passed in the run.
    - The full focused command finished with `1739 passed`, `11 failed`, and
    `6 timed out`; the failures were unrelated local app-server environment
    failures from `sandbox-exec: sandbox_apply: Operation not permitted`
    plus one missing `test_stdio_server` helper binary.
    - Installed an npm-published Codex plugin package through a throwaway
    local marketplace and throwaway `CODEX_HOME` to exercise the real npm
    materialization path end to end.
  • [codex] Use managed defaults for TUI threads (#30147)
    ## Why
    
    #29683 exposes managed defaults for new-thread model settings through
    `configRequirements/read` without applying them server-wide. The TUI is
    an app-server client, so it should explicitly consume those defaults
    when it creates a fresh thread.
    
    This lets plain `codex` start on the managed model while preserving the
    existing ability to change model settings within the thread.
    
    ## What changed
    
    - Read `requirements.models.newThread` during TUI app-server bootstrap.
    - Apply the managed model, reasoning effort, and service tier to the
    initial fresh thread and subsequent `/new` or `/clear` threads.
    - Keep explicit launch overrides above the managed defaults.
    - Normalize the managed `fast` service tier to the `priority` request
    value.
    - Leave resumed and forked threads unchanged.
    
    The application logic lives in a small TUI-only module; app-server
    `thread/start` behavior remains unchanged for other clients.
    
    ## User experience
    
    - Plain `codex` starts with the managed new-thread settings.
    - A user can still change settings with `/model` or the existing
    service-tier controls.
    - Starting another fresh thread reapplies the managed defaults.
    - Explicit launch choices such as `codex -m <model>` continue to win.
    
    ## Validation
    
    - `just test -p codex-tui managed_new_thread_defaults`
    - `just fix -p codex-tui`
    
    Depends on #29683.
  • [codex] Add managed new-thread model settings (#29683)
    ## Why
    
    Admins need persistent defaults for the model, reasoning effort, and
    service tier shown when the Desktop App creates a new thread. These are
    initialization defaults rather than runtime constraints: the App should
    use them to initialize its draft while still allowing a user to make an
    explicit selection.
    
    The app-server therefore needs to expose the managed values before
    thread creation without changing `thread/start` behavior for other
    clients.
    
    ## What changed
    
    - Parse `model`, `model_reasoning_effort`, and `service_tier` from
    `[models.new_thread]` in `requirements.toml`.
    - Compose the `models` requirements through the existing
    requirements-layer precedence rules.
    - Expose the resolved values through `configRequirements/read` as
    `requirements.models.newThread`.
    - Add the corresponding app-server protocol types and regenerate the
    JSON and TypeScript schema fixtures.
    - Document the new `configRequirements/read` fields in the app-server
    README.
    
    ## Scope
    
    This PR is data plumbing only. It does not apply these values during
    `thread/start` and does not change thread creation for existing
    app-server clients, resumed or forked sessions, internal or subagent
    sessions, `codex exec`, or the TUI. A companion Desktop App change owns
    draft initialization, sends the effective settings for ordinary and
    prewarmed starts, and preserves explicit user changes.
    
    ## Validation
    
    - Requirements deserialization coverage for `[models.new_thread]`
    - Requirements-layer precedence coverage
    - App-server API mapping coverage
    - `configRequirements/read` integration coverage
    - Regenerated app-server JSON and TypeScript schema fixtures
  • feat(app-server): add history_mode to thread (#29927)
    ## Description
    
    This PR adds a new `historyMode = "legacy" | "paginated"` to `Thread`.
    This will be stored in `SessionMeta` in the JSONL rollout file and as a
    new column in the SQLite thread_metadata table, and exposed on
    `thread/start` and on the `Thread` object in app-server.
    
    ## What changed
    
    - Added canonical `ThreadHistoryMode` with `legacy` and `paginated`,
    defaulting old and new SessionMeta to `legacy`.
    - Carried `history_mode` through core session config, ThreadStore stored
    metadata, local/in-memory stores, rollout metadata extraction, and the
    existing SQLite `threads` table.
    - Added experimental `historyMode` to app-server v2 `Thread` and
    `thread/start`.
    - Made paginated stored threads metadata-discoverable but unsupported
    for legacy full-history reads, `load_history`, live resume, and create
    paths.
    - Regenerated app-server schema fixtures and added
    protocol/state/thread-store/app-server coverage for persistence and
    fail-closed behavior.
    
    ## Compatibility floor
    Because users may be running various versions of Codex binaries on the
    same machine (TUI, Codex App, etc.), we will need to establish a
    compatibility floor for upcoming paginated threads, which will change
    how thread storage reads and writes work.
    
    The overall plan here:
    ```
    Release N:
    - Add historyMode to SessionMeta / Thread / SQLite metadata.
    - Teach binaries to understand paginated threads.
    - If a binary sees `historyMode="paginated"` but does not support the paginated contract, it refuses to resume/mutate the thread.
    - Default remains `"legacy"`.
    
    Release N+1:
    - First-party clients start opting into paginated threads where appropriate.
    - Internal dogfood / staged rollout.
    - Measure old-client usage and paginated-thread unsupported errors.
    
    Release N+2:
    - Only after Release N+ is overwhelmingly deployed, make paginated the default.
    - Accept that a small tail of N-1-or-older binaries may not understand paginated threads.
    ```
    
    The important behavior change is fail-closed handling for a binary that
    encounters a persisted `paginated` thread before it knows how to fully
    support paginated history. In app-server, if a thread is `paginated`, we
    will:
    
    - allow metadata-only discovery paths like `thread/list` and
    `thread/read(includeTurns=false)`, so clients can still see the thread
    and inspect its `historyMode`
    - reject legacy full-history/live-thread paths like
    `thread/read(includeTurns=true)` and `thread/resume` with an unsupported
    JSON-RPC error
    - avoid silently treating an unknown or future `historyMode` as `legacy`
    
    Under the hood, the ThreadStore layer also rejects legacy operations
    that would need to load or replay the full thread history for a
    paginated thread. That gives us the behavior we want for Release N:
    future paginated threads are visible, but this binary fails closed
    instead of trying to operate on them as if they were legacy threads.
  • [codex] Surface MCP reauthentication-required startup failures (#29877)
    ## Summary
    
    - distinguish expired, non-refreshable stored MCP OAuth credentials from
    first-time missing credentials
    - carry a typed `failureReason: "reauthenticationRequired"` on the
    existing `mcpServer/startupStatus/updated` notification only when user
    action is required
    - keep the public MCP auth-status API unchanged and regenerate the
    app-server protocol schemas and documentation
    
    ## Why
    
    An MCP server with an expired access token and no usable refresh token
    currently fails startup without giving clients a reliable, typed
    recovery signal.
    
    The existing startup-status notification is the natural place to carry
    this state. Its nullable `failureReason` keeps the recovery reason
    attached to the failed startup transition without adding a one-off
    notification. Internally, Codex distinguishes first-time login from
    reauthentication and emits the reason only when the startup error itself
    requires authentication.
    
    ## User impact
    
    App clients can prompt an existing user to reconnect an MCP server when
    automatic recovery is impossible by handling a failed
    `mcpServer/startupStatus/updated` notification whose `failureReason` is
    `reauthenticationRequired`. Starting, ready, cancelled, unrelated
    failures, and first-time setup carry no reauthentication reason.
    
    ## Companion app PR
    
    - openai/openai#1069582
    
    ## Validation
    
    - `just test -p codex-app-server-protocol` — 248 passed; schema fixture
    tests passed
    - `cargo check -p codex-app-server -p codex-tui`
    - `just test -p codex-rmcp-client -p codex-mcp` — 184 passed, 2 skipped
    - `just test -p codex-protocol -p codex-app-server-protocol -p
    codex-mcp` — 579 passed
    - `just write-app-server-schema`
    - `just fmt`
  • [codex] Add managed MCP server matchers (#29648)
    ## Summary
    
    This PR extends the existing managed `mcp_servers` identity requirement
    so that one name-qualified rule can use either:
    
    - the released exact command or URL identity;
    - an exact stdio executable with an exact-length, ordered argument
    matcher list; or
    - a direct MCP URL matcher.
    
    Matcher-based rules stay under the released `identity` key and use the
    same `McpServerRequirement` abstraction and `mcp_servers.<server_name>`
    namespace.
    
    ## Behavior
    
    Policy activation and name qualification are unchanged:
    
    - If `mcp_servers` is absent, ordinary configured MCP servers remain
    unrestricted.
    - If `mcp_servers` is present, a server needs a matching same-name
    requirement.
    - `mcp_servers = {}` continues to deny every configured MCP server.
    - Existing exact identity requirements keep their released semantics.
    
    Plugin-bundled MCP servers use the same requirement shapes under
    `plugins.<plugin_name>.mcp_servers.<server_name>`. Top-level non-empty
    rules continue to govern only ordinary configured servers; plugin rules
    remain explicitly plugin-scoped. The existing globally empty
    `mcp_servers = {}` plugin kill switch is preserved.
    
    Requirements layers continue to use the existing regular TOML merge
    behavior. Atomic replacement of named MCP requirements is intentionally
    out of scope here and is tracked independently in #30118.
    
    ## Requirement contract
    
    The released exact identity contract remains valid:
    
    ```toml
    [mcp_servers.docs.identity]
    command = "codex-mcp"
    
    [mcp_servers.remote.identity]
    url = "https://example.com/mcp"
    ```
    
    Command identities continue to check only `command`; they do not inspect
    arguments, `cwd`, `env`, or `env_vars`.
    
    A command matcher uses an exact executable plus an exact-length, ordered
    argument list. Each argument position supports `exact`, `prefix`, or
    full-value `regex` matching:
    
    ```toml
    [mcp_servers.internal_mcp_proxy.identity]
    command = { executable = "company-cli", args = [
      { match = "exact", value = "mcp" },
      { match = "exact", value = "proxy" },
      { match = "exact", value = "--server" },
      { match = "regex", expression = '^https://[A-Za-z0-9-]+\.mcp\.internal\.example\.com(?::443)?(?:/.*)?$' },
    ] }
    ```
    
    Direct streamable HTTP MCP definitions can use the same value matcher
    types through `identity.url`:
    
    ```toml
    [mcp_servers.internal_http.identity]
    url = {
      match = "regex",
      expression = '^https://[A-Za-z0-9-]+\.mcp\.internal\.example\.com(?:/.*)?$',
    }
    ```
    
    Plugin-bundled MCP matchers use the same contract inside the
    plugin-qualified allowlist:
    
    ```toml
    [plugins."sample@test".mcp_servers.internal_mcp_proxy.identity]
    command = { executable = "company-cli", args = [
      { match = "exact", value = "mcp" },
      { match = "exact", value = "proxy" },
    ] }
    ```
    
    Regexes are validated while managed requirements are loaded, and regex
    matching must cover the complete value. Command matchers constrain only
    the executable and arguments.
    
    ## Why
    
    Enterprise administrators need to allow MCP servers by executable and
    positional-argument shape, including fixed arguments plus constrained
    values such as internal MCP URLs passed to a proxy.
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `just test -p codex-config` (198 passed)
    - `just test -p codex-core mcp_servers_by_matchers --lib` (2 passed)
  • [codex] Add Ultra reasoning effort (#29899)
    ## Why
    
    Ultra should be one user-facing reasoning selection for work that
    benefits from both maximum reasoning and proactive multi-agent
    delegation. Without it, clients must coordinate maximum reasoning with
    the experimental `multiAgentMode` setting, even though the inference
    backend still expects its existing `max` effort value.
    
    This change makes reasoning effort the source of truth: clients select
    `ultra`, core derives proactive multi-agent behavior when the turn is
    eligible for multi-agent V2, and inference requests continue to use the
    backend-compatible `max` value.
    
    ## What changed
    
    - Add `ultra` as a first-class reasoning effort and preserve
    model-catalog ordering when exposing it to clients.
    - Convert `ultra` to `max` at the inference request boundary, including
    Responses HTTP/WebSocket requests, startup prewarm, compaction, and
    memory summarization.
    - Derive effective multi-agent mode per turn from effective reasoning
    effort:
      - eligible multi-agent V2 + `ultra` → `proactive`
      - eligible multi-agent V2 + any other effort → `explicitRequestOnly`
    - V1 or otherwise ineligible sessions → no multi-agent mode instruction
    - Keep the derived effective mode in turn context history so successive
    turns can emit a developer-message update only when the effective mode
    changes.
    - Remove selected multi-agent mode from core session configuration, turn
    construction, thread settings, resume/fork restoration, and subagent
    spawn plumbing. Subagents inherit reasoning effort and derive their own
    effective mode.
    - Retain the experimental app-server `multiAgentMode` fields for wire
    compatibility while marking them deprecated. Request values are accepted
    but ignored; compatibility response fields report `explicitRequestOnly`.
    - Display Ultra in the TUI using the order supplied by `model/list`.
    
    ## Validation
    
    - `just test -p codex-core ultra_reasoning_uses_max_for_requests`
    - `just test -p codex-tui model_reasoning_selection_popup`
  • TUI Plugin Sharing 5 - polish remote plugin catalog rows (#26705)
    This is the final plugin sharing PR in the 5-PR stack. It applies the
    remaining TUI polish for remote plugin catalog rows and tabs:
    admin-disabled plugins now read as blocked/view-only instead of looking
    toggleable, admin-installed/default-installed plugins count and sort
    like installed plugins, plugin search matches richer metadata, and an
    empty successful `Shared with me` section stays hidden.
    
    - Admin-disabled rows use a blocked marker, show `Disabled`, and keep
    Enter-only detail behavior without a toggle hint.
    - Admin-installed/default-installed plugins show as installed in counts,
    ordering, tabs, and detail copy.
    - Plugin search now matches descriptions and keywords in addition to
    existing row metadata.
    - Successful-empty `Shared with me` tabs are hidden, while loading,
    error, workspace-empty, and real shared-plugin states remain visible.
    - Updates coverage in
    `plugins_popup_snapshot_shows_all_marketplaces_and_sorts_installed_then_name`,
    `plugins_popup_admin_disabled_installed_plugin_has_no_toggle_hint`,
    `plugins_popup_search_matches_plugin_descriptions`, and
    `plugins_popup_remote_section_fallback_states_snapshot`.
    - Updates snapshots `plugins_popup_curated_marketplace` and
    `plugins_popup_empty_shared_section_hidden`.
    
    
    <img width="2034" height="106" alt="image"
    src="https://github.com/user-attachments/assets/3f9a57e1-edd8-4e6c-b0b0-9f632a3c9529"
    />
    <img width="2038" height="380" alt="image"
    src="https://github.com/user-attachments/assets/45a47491-3381-4846-a13d-496bc0051d42"
    />
  • [apps] Thread structured icon assets through app list (#29889)
    ## Summary
    
    - Add `iconAssets` and `iconDarkAssets` to the app-list protocol.
    - Preserve structured icons through directory merging and the connector,
    app-
      server, and TUI boundaries.
    - Keep legacy logo URLs unchanged as compatibility fallbacks.
    - Update generated protocol schemas and TypeScript types.
  • feat(app-server): list descendant threads by ancestor (#29591)
    ## Why
    
    `thread/list` can filter direct children with `parentThreadId`, but
    clients cannot request an entire spawned subtree. Discovering every
    descendant requires repeated client-side requests and gives up the
    database's existing filtering and pagination path.
    
    ## What changed
    
    Experimental clients can use `ancestorThreadId` to return strict
    descendants at any depth while `parentThreadId` retains its direct-child
    meaning. The filters are mutually exclusive, the ancestor is excluded,
    and every result preserves its immediate `parentThreadId` so callers can
    reconstruct the tree.
    
    ## How it works
    
    - **Explicit relationship:** Internal list parameters distinguish direct
    children from transitive descendants without changing the meaning of
    `parentThreadId`.
    - **Existing graph:** Persisted parent-child spawn edges remain the
    source of truth, so descendant lookup needs no schema migration or
    ancestry cache.
    - **Indexed traversal:** A recursive SQLite query starts from the
    parent-edge index, walks each generation, and applies thread filters,
    sorting, and cursor pagination in the same database request.
    - **Reconstructable results:** The response stays flat and normally
    ordered while carrying each descendant's immediate parent.
    
    ## Verification
    
    Ran 550 tests across the protocol, state, rollout, and thread-store
    crates, then reran the four focused state, store, and app-server
    descendant-listing tests after the final diff reduction. Scoped Clippy
    and formatting checks passed. Stable and experimental schema generation
    was checked; the stable fixtures remain unchanged while the experimental
    schema includes the new field.
  • [codex] suppress low usage remaining warnings when credits are available (#28593)
    ## Why
    
    The TUI computed proactive `Heads up, you have less than ...` warnings
    before considering workspace credits. As a result, users could see
    included-limit warnings even when they could continue using Codex with
    workspace credits.
    
    `has_credits` alone is not sufficient to determine whether finite
    credits are usable: a spend-control hard limit can cap the reported
    balance to zero while `has_credits` still reflects the workspace's raw
    balance. Unlimited credits are the opposite case: they are usable even
    though no numeric balance is reported.
    
    ## What changed
    
    - suppress proactive TUI rate-limit usage warnings and the lower-cost
    model nudge when usable workspace credits are available
    - treat credits as usable when `has_credits` is true and either
    `unlimited` is true or the parsed balance is positive
    - continue showing warnings when the usable balance is zero, including
    when a spend-control limit has capped otherwise available workspace
    credits
    - add regression coverage for zero-balance, positive-balance, and
    unlimited workspace-credit snapshots
    
    ## Validation
    
    - `just test -p codex-tui rate_limit_usage_warnings_`
  • [codex] show external import result counts (#29567)
    ## What changed
    
    - Show per-type import counts in the `/import` review UI and started
    message.
    - Render completion results as a multi-line summary with total
    imported/failed counts and one row per import type.
    - Add snapshot coverage for the updated review and completion output.
    
    <img width="537" height="322" alt="Screenshot 2026-06-23 at 9 41 20 PM"
    src="https://github.com/user-attachments/assets/166542eb-2097-4b2b-8130-8f6fd8c680ce"
    />
    
    
    ## Why
    
    The TUI previously only reported that Claude Code import started or
    finished. Users could not see how many items of each type were selected
    or how many actually imported versus failed.
  • connectors: own app metadata types (#29723)
    ## Why
    
    Connector metadata is consumed by connector discovery, ChatGPT
    integration, core, and TUI code. Treating app-server's wire DTO as the
    shared domain model reverses the intended dependency direction.
    
    ## What changed
    
    - Added connector-owned app branding, review, screenshot, metadata, and
    info types.
    - Added explicit conversions in app-server and TUI while preserving
    app-server's wire payloads.
    - Removed production app-server-protocol dependencies from connectors
    and ChatGPT connector code.
    
    ## Stack
    
    This is PR 4 of 6, stacked on [PR
    #29722](https://github.com/openai/codex/pull/29722). Review only the
    delta from `codex/split-config-layer-types`. Next: [PR
    #29724](https://github.com/openai/codex/pull/29724).
    
    ## Validation
    
    - Connector and tools coverage passed.
    - App-server app-list coverage passed: 13 tests.
  • config: own layer provenance types (#29722)
    ## Why
    
    Config layer provenance describes how effective configuration was
    assembled, so it belongs with the config loader rather than in
    app-server's serialized API types.
    
    ## What changed
    
    - Moved `ConfigLayerSource`, `ConfigLayerMetadata`, and `ConfigLayer`
    ownership into `codex-config`.
    - Kept app-server's wire payloads unchanged and added explicit
    conversions at the app boundary.
    - Removed lower-level app-server-protocol dependencies from config
    consumers.
    
    ## Stack
    
    This is PR 3 of 6, stacked on [PR
    #29721](https://github.com/openai/codex/pull/29721). Review only the
    delta from `codex/split-auth-domain-types`. Next: [PR
    #29723](https://github.com/openai/codex/pull/29723).
    
    ## Validation
    
    - `codex-config` coverage passed.
    - App-server config-manager and config RPC coverage passed.
  • auth: move domain mode below app wire types (#29721)
    ## Why
    
    Authentication mode is a domain concept used by login, model selection,
    telemetry, and transports. Keeping the canonical type in app-server
    protocol forces those lower-level crates to depend on an unrelated wire
    API.
    
    ## What changed
    
    - Added canonical `codex_protocol::auth::AuthMode` domain values.
    - Kept the app-server wire DTO unchanged and added an explicit app-side
    conversion.
    - Removed production app-server-protocol dependencies from login,
    model-provider-info, models-manager, and otel call paths.
    
    ## Stack
    
    This is PR 2 of 6, stacked on [PR
    #29714](https://github.com/openai/codex/pull/29714). Review only the
    delta from `codex/split-json-rpc-protocols`. Next: [PR
    #29722](https://github.com/openai/codex/pull/29722).
    
    ## Validation
    
    - Auth and login coverage passed in the focused protocol/domain test
    run.
    - App-server account and auth conversion coverage passed.
  • [plugins] Add marketplace source requirements (#29690)
    ## Why
    
    Managed deployments need a mergeable way to declare which marketplace
    sources Codex may use. An enterprise-keyed TOML table avoids array merge
    ambiguity and lets every requirements layer use the existing config
    precedence rules without a marketplace-specific merger.
    
    ## Requirements shape
    
    ```toml
    [marketplaces]
    restrict_to_allowed_sources = true
    
    [marketplaces.allowed_sources.company_plugins]
    source = "git"
    url = "https://github.com/example/company-plugins.git"
    ref = "main"
    
    [marketplaces.allowed_sources.internal_git]
    source = "host_pattern"
    host_pattern = "^git\\.example\\.com$"
    
    [marketplaces.allowed_sources.local_plugins]
    source = "local"
    path = "/opt/company/codex-plugins"
    ```
    
    `restrict_to_allowed_sources` follows normal scalar precedence.
    `allowed_sources` follows normal recursive TOML table merge behavior:
    distinct keys accumulate and fields under the same key use normal layer
    precedence. The final `source` value later selects which fields the
    marketplace admission policy interprets.
    
    The raw rule fields remain optional while requirements layers are
    composed, so a higher-priority layer can override only `ref`, `url`, or
    another individual field. Source-specific validation and normalization
    intentionally belong to the marketplace admission layer, not
    requirements merging.
    
    This initial shape includes `git`, `host_pattern`, and `local` sources.
    It does not add npm or path-pattern rules.
    
    ## What changed
    
    - Add the marketplace requirements TOML shape to
    `ConfigRequirementsToml`, `ConfigRequirementsWithSources`, and
    `ConfigRequirements`.
    - Carry marketplace requirements through the existing regular
    requirements merge path.
    - Keep allowed-source entries as raw partial tables for downstream
    policy interpretation.
    - Cover partial same-key overlays, source changes, unknown fields, and
    unmodified local paths.
    
    This PR defines and composes the requirements only. Source admission is
    implemented by the next PR in the stack.
    
    ## Stack
    
    This is PR 1 of 3. #29753 adds source admission on top of this PR; draft
    #29691 will add runtime enforcement after it is rebased later.
    
    ## Test plan
    
    - `just test -p codex-config marketplace_`
  • core: resolve view_image paths in selected environment (#29526)
    ## Why
    
    view_image needs to support foreign OS remote executors.
    
    ## What
    
    - resolve image paths against the selected environment as `PathUri` and
    read them through that environment's filesystem
    - keep app-server's public path field wire-compatible as
    `LegacyAppPathString`, with purpose-specific UI rendering
    - cover relative and absolute target-native paths in the core
    integration test and run the full `view_image` suite under wine-exec
    without skips
  • core: add extra metadata field to Thread struct (#29675)
    # Summary
    
    Adds a field Thread.extras that can be used to hold arbitrary metadata
    specific to a given thread.
  • mcp: accept foreign absolute cwd for remote stdio (#29493)
    ## Why
    
    Remote stdio MCP servers can run in an environment whose path convention
    differs from the Codex host. A Windows cwd such as
    `C:\Users\openai\share` is absolute for the executor but was rejected by
    a POSIX orchestrator.
    
    Built on #29501, now merged, which only clarifies the host-native
    `PathUri` constructor name.
    
    ## What changed
    
    - Deserialize MCP cwd values as `LegacyAppPathString` so config does not
    apply host path rules.
    - Interpret that spelling as host-native for local launches and convert
    it to `PathUri` at executor launch.
    - Skip host filesystem and command resolution checks for remote stdio in
    `codex doctor`.
    - Add host-independent config and executor-boundary coverage using the
    foreign path convention for each test platform.
    
    ## Validation
    
    - `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
    codex-rmcp-client` (408 passed)
    - `just test -p codex-cli -p codex-rmcp-client` (372 passed)
    - `cargo check --workspace --tests`
    - `just test` (11,311 passed; 43 unrelated environment/timing failures)
    - `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
    codex-mcp-extension -p codex-rmcp-client -p codex-tui`
  • chore: improve expired Bedrock credential errors (#28992)
    ## Why
    
    Amazon Bedrock returns a `401 Unauthorized` response containing
    `Signature expired:` when an AWS credential, including a short-lived
    `AWS_BEARER_TOKEN_BEDROCK`, has expired. Codex currently surfaces that
    response as a generic `unexpected status` error, which does not explain
    how to recover.
    
    Environment-provided bearer tokens cannot be refreshed automatically, so
    the error should direct users to refresh their AWS credentials or
    replace or remove the environment token and restart Codex. This
    classification belongs to the Amazon Bedrock provider so similar
    responses from other providers retain their existing behavior.
    
    ## What changed
    
    - Add a synchronous `ModelProvider::map_api_error` hook that defaults to
    the existing provider-neutral API error mapping, and route model
    request, stream, WebSocket, and terminal unauthorized errors through the
    active provider.
    - Override the hook for Amazon Bedrock. After preserving the structured
    status, body, URL, and request metadata, recognize `401` responses
    containing `Signature expired:` and attach actionable credential
    guidance.
    - Keep `codex-protocol` provider-neutral by representing the guidance as
    an optional `user_message`. Error rendering prefers this message while
    continuing to append the URL, request ID, Cloudflare ray, and
    authorization diagnostics.
    - Add model-provider coverage for expired signatures and negative cases,
    core coverage for provider dispatch after unauthorized recovery, and a
    TUI snapshot for the rendered error.
    
    ## Testing
    Tested with a real request with expired bedrock key:
    <img width="962" height="126" alt="Screenshot 2026-06-22 at 3 56 51 PM"
    src="https://github.com/user-attachments/assets/7e21cc7c-798e-4662-8467-7f304a2f2b59"
    />
  • TUI Plugin Sharing 4 - cover remote plugin catalog flows (#26704)
    Remote plugin catalogs now span workspace, shared, and local sources, so
    their TUI behavior needs focused regression coverage across loading,
    navigation, actions, and refreshes.
    
    This PR:
    
    - Covers product labels and rendered loading/error states for Workspace,
    Shared with me, Shared with me (link), and Local tabs, including tab
    persistence across refresh and detail navigation.
    - Covers remote/local deduplication, Installed-tab remote detail
    routing, marketplace load-error handling, and disabled install/uninstall
    navigation.
    - Adds a full detail snapshot for local shared-plugin metadata plus
    focused snapshots for marketplace labels and admin-disabled status.
    - Verifies shared plugins remain eligible for mentions and successful
    uninstalls trigger a catalog refresh.
  • [plugins] Add dark-mode logo metadata (#29488)
    Adds additive dark-mode plugin logo metadata across manifests, remote
    catalogs, and the app-server protocol while keeping uninstalled Git
    listings free of synthetic local paths.
    
    Supersedes #28945. This replacement uses an upstream branch so trusted
    CI can use the repository-provided remote Bazel configuration.
    
    ## Current state
    
    Plugin interfaces expose only the default logo asset. Clients therefore
    cannot select a dedicated dark-mode logo even when a plugin provides
    one.
    
    ## What this PR changes
    
    - Adds nullable `logoDark` and `logoUrlDark` fields to
    `PluginInterface`.
    - Resolves local `interface.logoDark` assets and maps remote
    `logo_url_dark` values.
    - Removes path-backed interface assets, including `logoDark`, from
    uninstalled Git fallback listings until the plugin has a real local
    root.
    - Updates the bundled plugin validator and manifest reference.
    - Regenerates the app-server JSON schemas and TypeScript types.
    
    Local manifests expose `interface.logoDark` as a package-relative asset
    path. Remote catalog responses expose `logo_url_dark`. These values map
    into separate app-server fields so clients can preserve local-path and
    remote-URL handling.
    
    ## Risk
    
    The fields are additive and nullable, so existing clients retain their
    current logo behavior. The main risks are an incomplete mapping path or
    exposing a synthetic local path for an uninstalled Git plugin.
    Local-manifest, remote-catalog, fallback-listing, protocol
    serialization, and app-server integration tests cover those paths.
    
    Spiciness: 2/5
    
    ## Testing
    
    - `just write-app-server-schema`
    - `just fmt`
    - Regression test first failed with `logo_dark` resolved to
    `/assets/logo-dark.png`, then passed after the fallback-listing fix.
    - `just test -p codex-core-plugins` (267 tests passed)
    - `just test -p codex-app-server 'suite::v2::plugin'` (114 tests passed)
    - `just test -p codex-app-server-protocol -p codex-core-plugins -p
    codex-plugin -p codex-skills` (517 tests passed before the follow-up)
    - `just test -p codex-tui plugin` (47 tests passed)
    - Validated a local plugin manifest containing `interface.logoDark` with
    the bundled validator.
    
    ## Manual verification
    
    Create a local plugin with both `interface.logo` and
    `interface.logoDark`, then call `plugin/list` or `plugin/read`. Confirm
    the response contains separate `logo` and `logoDark` paths. For a remote
    catalog entry, confirm `logoUrlDark` is populated from `logo_url_dark`.
    For an uninstalled Git marketplace entry, confirm path-backed interface
    assets remain absent until installation.
    
    Issue: N/A - coordinated maintainer change.
  • fix(config): address permission profile review follow-ups (#29479)
    ## Summary
    
    - rename `Config::permission_profile_allowed` to
    `is_permission_profile_allowed`
    - use `BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS` in the TUI and
    its assertion
    - follow up on the late review comments from #26678
    
    The previous `:danger-no-sandbox` value was an invalid built-in profile
    ID. #26678 corrected it to `:danger-full-access`; this PR centralizes
    the value to prevent future drift.
    
    ## Testing
    
    - Not run per request; `cargo fmt` only
    
    Co-authored-by: Codex <noreply@openai.com>
  • permission profiles: expose availability to clients (#26678)
    ## Why
    
    `permissionProfile/list` currently advertises every built-in and
    configured profile even when effective enterprise requirements prevent
    selecting it. That forces each client to reconstruct policy from
    lower-level requirement fields, which is easy to miss and difficult to
    keep consistent.
    
    The catalog should remain complete so clients can explain that an option
    was disabled by an administrator, while also reporting whether each
    profile is selectable.
    
    ## What
    
    - Add an `allowed` field to each permission profile summary.
    - Build a shared catalog from the effective config and current
    requirements, including `allowed_sandbox_modes`, `allowed_permissions`,
    and filesystem restrictions.
    - Use the shared catalog in app-server and the TUI so disallowed
    profiles remain visible but cannot be selected.
    - Use the canonical `:danger-full-access` profile ID in the TUI.
    - Update the app-server schemas, API documentation, behavioral tests,
    and TUI snapshots.
    
    ## Scope
    
    This PR targets `main` directly and is independent of #24852. It
    preserves the current behavior where built-in profiles are constrained
    by sandbox-mode requirements and `allowed_permissions` applies to
    configured profiles.
    
    ## Testing
    
    - `just test -p codex-core
    permission_profile_catalog_marks_profiles_disallowed_by_requirements`
    - `just test -p codex-app-server permission_profile_list`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-tui profile_permissions`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
    - `just fmt`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
    Co-authored-by: Joey Trasatti <joey.trasatti@openai.com>
  • Allow ChatGPT accounts without email (#28991)
    # Summary
    
    Codex required every ChatGPT account to have an email address. A
    service-account personal access token can return valid account metadata
    without one, so PAT login failed while decoding the metadata response.
    
    This change makes email optional in the account metadata type that owns
    it and preserves that absence through authentication, provider account
    state, the app-server API, generated clients, and TUI bootstrap.
    Existing accounts with email addresses keep the same behavior.
    
    ## Behavior-changing call sites
    
    | Call site | Behavior after this change |
    | --- | --- |
    | `login/src/auth/personal_access_token.rs` | PAT metadata accepts a
    missing or null email and retains `None`. |
    | `agent-identity/src/lib.rs` | Agent Identity JWT claims accept an
    omitted email. |
    | `login/src/auth/storage.rs` and `login/src/auth/agent_identity.rs` |
    Stored and managed Agent Identity records carry `Option<String>`.
    Deserialization maps the legacy empty-string sentinel to `None`. |
    | `login/src/auth/manager.rs` | `get_account_email` returns the stored
    option, and managed identity bootstrap no longer converts `None` to an
    empty string. |
    | `model-provider/src/provider.rs` and `protocol/src/account.rs` | A
    ChatGPT provider account requires a plan type but may carry no email. |
    | `app-server-protocol/src/protocol/v2/account.rs` | `account/read`
    keeps the `email` field on the wire and returns `null` when the account
    has no email. Generated TypeScript and JSON schemas describe a required,
    nullable field. |
    | `sdk/python/src/openai_codex/generated/v2_all.py` | The generated
    Python `ChatgptAccount` model accepts `None` for email. |
    | `tui/src/app_server_session.rs` | Email-less ChatGPT accounts
    bootstrap normally, keep external feedback routing, omit account-email
    telemetry, and display the plan in account status. |
    
    ## Design decisions
    
    - Missing email remains `None` at every layer. The code never uses an
    empty string as a substitute.
    - The app-server response includes `"email": null` instead of omitting
    the field. Clients retain a stable response shape.
    - Plan type remains required for provider account state. This change
    relaxes only the email assumption.
    
    ## Testing
    
    Tests: affected test targets compile, scoped Clippy and formatting pass,
    a focused TUI snapshot covers plan-only account status, real
    before/after PAT login smoke covers metadata without email, app-server
    smoke covers `account/read` with `email: null`, and a regression smoke
    covers an existing email-bearing PAT. Unit tests run in CI.
    
    ## Evidence
    
    Visual smoke evidence will be attached here.
  • PAC 2 - Add shared auth system proxy contract (#26707)
    ## Summary
    
    Stacked on #26706.
    
    Adds the shared auth/system-proxy contract that later platform resolver
    PRs plug into. This PR moves Codex-owned auth and startup HTTP clients
    through a common route-aware boundary, but does not yet add Windows or
    macOS system proxy resolution.
    
    The default path remains unchanged when `respect_system_proxy` is absent
    or disabled.
    
    ## Implementation
    
    - Adds `codex-client/src/outbound_proxy.rs` with the shared
    route-selection model:
      - `OutboundProxyConfig`;
      - `ClientRouteClass`;
      - `RouteFailureClass`;
      - `build_reqwest_client_for_route`.
    - Preserves the existing reqwest/default-client behavior when no route
    config is supplied.
    - Uses the fixed MVP routing policy when route config is supplied:
    platform system/PAC/WPAD discovery, then explicit env proxy variables,
    then direct connection.
    - Keeps platform-specific system discovery behind the shared client
    boundary. This PR provides the contract and fallback behavior; later
    resolver PRs plug in Windows and macOS discovery.
    - Adds `login::AuthRouteConfig` so auth call sites depend on a small
    policy type instead of platform resolver details.
    - Maps the resolved `Config.respect_system_proxy` boolean into
    `AuthRouteConfig` for auth-owned clients.
    - Wires the route config through browser login, device-code login,
    access-token login, login status, logout/revoke, token refresh, API-key
    exchange, app-server account login, TUI/app startup, cloud-config
    bootstrap, cloud tasks, plugin auth, and exec startup config loading.
    
    ## End-user behavior
    
    - No behavior changes by default.
    - When `respect_system_proxy = true`, auth-owned clients opt into the
    shared route-aware client path.
    - On platforms without a resolver implementation in this PR, system
    discovery is unavailable and the route-aware path falls back to explicit
    env proxy handling, then direct connection.
    - Custom CA handling remains separate from proxy route selection and
    still runs through the shared client builder.
    - No proxy URLs, PAC contents, or resolved platform details are exposed
    through the public config surface introduced here.
    
    ## Tests
    
    Adds or updates coverage for:
    
    - preserving default auth-client fallback behavior when no route config
    is provided;
    - injected environment-proxy fallback without mutating process
    environment;
    - existing login-server E2E flows using explicit `auth_route_config:
    None` to guard unchanged default behavior;
    - updated auth manager, login, logout, cloud-config, startup, and
    plugin-auth call sites passing route config explicitly.
  • [codex] Fix usage-limit reset copy and state (#28793)
    ## Why
    
    The reset flow introduced in #28154 still describes earned reset credits
    as "rate-limit resets" and uses generic reset-scope copy. It can also
    retain a stale available-credit count after redemption or an account
    change, leaving the reset action enabled after the last credit is used.
    
    This follow-up updates terminology only within that reset feature.
    Existing rate-limit wording elsewhere in the CLI and TUI is unchanged.
    
    ## What changed
    
    - Rename reset-specific `/usage` menu items, startup hints, and reset
    dialogs to "usage limit reset."
    - Describe monthly resets for Free, Go, and accounts that report a
    monthly usage window; otherwise describe the current 5-hour and weekly
    limits.
    - Recheck a cached zero balance when `/usage` is reopened, and refresh
    the balance after redemption so the final reset immediately disables the
    action.
    - Correlate async refresh results before updating snapshots and clear
    account-derived reset state, warnings, prompts, and status surfaces when
    the account changes.
    
    ## Validation
    
    - `just test -p codex-tui chatwidget::tests::usage` — 29 passed.
    - `just test -p codex-tui chatwidget::tests::status_command_tests` — 7
    passed.
    - Account-boundary prompt and plan-mode prompt regression tests passed.
    - `cargo insta pending-snapshots` from `codex-rs/tui` — no pending
    snapshots.\
    
    <img width="814" height="318" alt="image"
    src="https://github.com/user-attachments/assets/2a460e96-458b-4805-8d9f-c759382d21a4"
    />
    view for monthly
    <img width="905" height="243" alt="image"
    src="https://github.com/user-attachments/assets/179f88e3-08fb-4af5-8dc6-ce6a944ed681"
    />
  • core: rename metadata -> internal_chat_message_metadata_passthrough (#28968)
    ## Description
    This PR cuts Codex over from generic `ResponseItem.metadata` (introduced
    here: https://github.com/openai/codex/pull/28355) to
    `ResponseItem.internal_chat_message_metadata_passthrough`, which is the
    blessed path and has strongly-typed keys.
    
    For now we have to drop this MAv2 usage of `metadata`:
    https://github.com/openai/codex/pull/28561 until we figure out where
    that should live.
  • TUI Plugin Sharing 3 - render remote plugin catalog sections (#26703)
    ## Summary
    
    [#26701](https://github.com/openai/codex/pull/26701) added remote plugin
    identity support, [#26702](https://github.com/openai/codex/pull/26702)
    added remote-section fetching and state, and
    [#28768](https://github.com/openai/codex/pull/28768) extracted the
    catalog rendering module. This PR builds the product-facing `/plugins`
    catalog on that foundation so remote records appear as OpenAI Curated,
    Workspace, and Shared with me sections rather than backend marketplace
    implementation details.
    
    Plugin details remain read-only for sharing metadata. This PR does not
    add share-authoring actions or change the app-server protocol.
    
    ## Changes
    
    - Renders OpenAI Curated, Workspace, and Shared with me sections with
    loading, empty, and error states.
    - Preserves section selection and stable tab ordering as remote sections
    transition between fallback and populated states.
    - Shows OpenAI Curated loading only when the explicit vertical fallback
    request was issued.
    - Centralizes remote marketplace identity matching around the existing
    marketplace constants.
    - Uses product labels for remote marketplaces and identifies the
    personal marketplace as Local by its path.
    - Shows read-only source, authentication, version, and sharing metadata
    in plugin detail views.
    - Applies narrow display deduplication for local and remote records
    sharing a remote plugin ID:
      - installed records take precedence;
    - local mapped sources are preferred for details only when their
    installed state matches the selected record.
    - Returns from detail and confirmation views through the current plugin
    cache so newly loaded remote sections are not overwritten by an older
    captured response.
    - Keeps admin-disabled plugins view-only and labels default-installed
    plugins as Available by default.
    
    ## Tests
    
    New tests:
    
    - `plugins_popup_admin_disabled_available_plugin_has_view_only_hint`
    - `plugins_popup_remote_section_fallback_states_snapshot`
    -
    `plugins_popup_installed_remote_row_keeps_remote_detail_when_local_share_is_uninstalled`
    
    Updated existing plugin catalog tests and snapshots for product labels,
    detail metadata, personal-marketplace labeling, and stable tab ordering.
    
    Verification:
    
    - `cargo clippy -p codex-tui --all-targets -- -D warnings`
    
    ## Follow-ups
    
    - Local/remote duplicate normalization should eventually move into
    app-server. This PR intentionally keeps the compatibility behavior
    narrow and display-only.
    - PR5 will sanitize sensitive components before displaying Git source
    URLs.
  • Filter noisy targets from persistent logs (#29457)
    ## Why
    
    The local SQLite log sink currently enables TRACE for every target. This
    persists high-volume dependency logs bridged through `target=log` and
    duplicates OpenTelemetry mirror events in `codex_otel.log_only` and
    `codex_otel.trace_safe`.
    
    These records rapidly consume the per-partition log budget and cause
    unnecessary SQLite insert-and-prune churn.
    
    ## What changed
    
    - Keep TRACE persistence for other targets.
    - Exclude bridged `target=log` events from the SQLite sink.
    - Exclude the two `codex_otel` mirror targets from the SQLite sink.
    - Share the same filter between app-server and TUI.
    
    Remote OpenTelemetry export and metrics are unchanged.
  • Add workspace messages app-server API (#29001)
    ## Summary
    
    - Add backend-client types and fetch support for active workspace
    messages.
    - Add the app-server v2 `account/workspaceMessages/read` method,
    generated schemas, and README documentation.
    - Delegate workspace-message eligibility to the Codex backend feature
    gate; map a backend 404 to `featureEnabled: false`.
    
    ## Testing
    
    - `just write-app-server-schema`
    - `just test -p codex-backend-client`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server workspace_messages`
    - `just fix -p codex-backend-client -p codex-app-server-protocol -p
    codex-app-server`
    - `just fmt`
    
    ## Stack
    
    - Base PR for #28232, which adds the TUI status-line integration.
  • Persist session IDs across thread resume (#29327)
    ## Summary
    
    A cold-resumed subagent kept its durable thread ID but could receive a
    new session ID, splitting one agent tree across multiple sessions after
    a restart.
    
    Persist the root session ID in every rollout `SessionMeta`, carry it
    through thread creation, and restore it before initializing the resumed
    `Session` and `AgentControl`.
    
    ## Behavior
    
    For a nested agent tree:
    
    ```text
    root session R
      parent thread P
        child thread C
    ```
    
    The child rollout stores:
    
    ```text
    session_id:       R
    parent_thread_id: P
    id:               C
    ```
    
    After a cold resume, the child still belongs to root session `R` while
    its immediate parent remains `P`. The integration coverage uses distinct
    values for all three IDs so it catches restoring the session from
    `parent_thread_id`.
    
    ## Legacy rollouts
    
    Previous rollouts have `id` but no `session_id`. `SessionMetaLine`
    deserialization treats a missing `session_id` as `id`, keeping those
    files readable, listable, and resumable. When a legacy subagent is
    resumed through its root, that synthesized child ID no longer overrides
    the inherited root-scoped `AgentControl`. New rollouts always persist
    the explicit root session ID.
  • Propagate safety buffering events to app-server clients (#29371)
    Responses API safety buffering metadata currently stops at the transport
    boundary, so app-server clients cannot render the in-progress safety
    review state.
    
    This change:
    - decodes and deduplicates `safety_buffering` metadata from Responses
    API SSE and WebSocket events without suppressing the original response
    event
    - emits a typed core event containing the requested model plus backend
    use cases and reasons
    - forwards that event as `turn/safetyBuffering/updated` through
    app-server v2 and updates generated protocol schemas
    - keeps the side-channel event out of persisted rollouts and turn timing
    
    This supports the Codex Apps buffering UX and depends on the Responses
    API backend work in https://github.com/openai/openai/pull/1044569 and
    https://github.com/openai/openai/pull/1044571.
    
    Validation:
    - focused `codex-core` safety-buffering integration test passes
    - `cargo check -p codex-core -p codex-app-server -p
    codex-app-server-protocol`
    - `just fix -p codex-api -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-rollout -p
    codex-rollout-trace -p codex-otel`
    - `just fmt`
    - broad package test run: 4,430/4,492 passed; 62 unrelated
    local-environment/concurrency failures involved unavailable test
    binaries, MCP subprocess setup, and app-server timeouts
  • Allow resume and settings commands during tasks and MCP startup (#29154)
    ## Why
    
    The TUI treats both an active turn and MCP startup as a running task.
    That currently blocks `/resume` and several settings commands even
    though they do not compete with turn execution, which is especially
    frustrating when MCP startup is slow.
    
    Model, permissions, personality, and service-tier selections already
    update thread settings independently of the running turn. Other clients
    can send those updates mid-turn, while the current turn continues with
    its captured settings. Allowing the same updates from local slash
    commands makes the TUI consistent with that existing behavior.
    
    ## What changed
    
    - Allow `/resume` while a task is running.
    - Allow `/model`, `/permissions`, `/personality`, and service-tier
    commands such as `/fast` while a task is running.
    - Keep the existing behavior where the active turn uses its captured
    settings and updates apply to subsequent turns.
    - Exercise the commands under the busy state in the existing TUI tests
    and retain coverage for commands that should remain blocked.
    
    ## Behavior note
    
    Turn settings such as model selection and reasoning effort are captured
    when a turn starts. Changing them during an active turn affects the next
    turn, not the turn already in progress. The status bar updates
    immediately, so it may temporarily display the newly selected setting
    before that setting is actually in effect.
    
    ## Verification
    
    - Focused `codex-tui` tests for resume dispatch, settings popups,
    `/fast`, and disabled-command behavior.
    
    ## Related issues
    
    Closes #19015.
    
    Addresses the next-turn-safe model/reasoning switching portion of
    #14356; dedicated shortcuts and the proposed depth meter remain out of
    scope.
  • Expose thread-level multi-agent mode (#28792)
    ## Why
    
    Once multi-agent mode can be selected per turn, clients also need to
    choose the initial selection when creating a thread and observe that
    selection through lifecycle and settings APIs.
    
    The selected value is intentionally distinct from the effective
    model-visible value: no client selection is represented as `null`, even
    though an eligible multi-agent v2 turn derives `explicitRequestOnly` as
    its effective default.
    
    ## What changed
    
    - Add the optional experimental `thread/start.multiAgentMode` parameter
    and pass it through thread creation.
    - Preserve an omitted initial value as an unset selection rather than
    eagerly storing `explicitRequestOnly`.
    - Apply an explicit `thread/start` selection to the first turn through
    the session configuration established at thread creation.
    - Restore the latest persisted effective mode as the selected baseline
    on cold resume when rollout history contains one.
    - Inherit the optional selected mode from a loaded parent when creating
    related runtime threads.
    - Return the current selected `multiAgentMode` from `thread/start`,
    `thread/resume`, `thread/fork`, and thread settings, using `null` when
    no mode is selected.
    - Keep lifecycle reporting independent from model capability and feature
    eligibility; core turn construction remains responsible for calculating
    and persisting the effective mode.
    
    ## Not covered
    
    - Clearing an existing loaded-session selection back to unset through
    `turn/start`; omitted or `null` currently retains the session's
    selection.
    - A TUI control, slash command, or `config.toml` preference.
    
    ## Verification
    
    - `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol`
    - `CARGO_INCREMENTAL=0 just test -p codex-app-server multi_agent_mode`
    
    The focused app-server coverage verifies explicit `thread/start`
    initialization, first-turn prompting, nullable reporting for an omitted
    selection, and retention of selections that are not currently
    runtime-eligible.
    
    ## Stack
    
    Stacked on #28685. This PR contains only the thread initialization and
    lifecycle/settings API layer.
  • Add per-turn multi-agent mode (#28685)
    ## Why
    
    Multi-agent v2 currently carries an explicit-request-only delegation
    rule in its static usage hint. That provides a safe default, but it
    prevents clients from selecting proactive delegation per turn without
    changing static guidance or rewriting prior model context.
    
    This change makes delegation mode a session selection that can be
    updated through `turn/start`, while deriving the effective model-visible
    mode separately for each turn. Eligible multi-agent v2 turns remain
    explicit-request-only unless proactive mode is both selected and
    enabled.
    
    ## What changed
    
    - Add the experimental `turn/start.multiAgentMode` parameter with
    `explicitRequestOnly` and `proactive` values. Omission retains the
    loaded session's current optional selection.
    - Add the default-off `features.multi_agent_mode` feature gate. Eligible
    multi-agent v2 turns use the selected mode when enabled; an unset
    selection or disabled gate resolves to `explicitRequestOnly`.
    - Treat mode prompting as inapplicable for multi-agent v1 and other
    unsupported session configurations, producing no multi-agent mode
    developer message rather than rejecting the turn.
    - Move the explicit-request-only rule out of the static v2 usage hint
    and into a bounded, tagged developer context fragment.
    - Emit the effective mode in initial context and only when that
    effective mode changes on later turns.
    - Persist the effective mode in `TurnContextItem` as the durable
    baseline for resume and context-update comparisons.
    
    Historical rollout items are not rewritten. Later mode developer
    messages establish the current rule incrementally.
    
    ## Not covered
    
    - Initial selection through `thread/start` and selected-mode reporting
    from thread lifecycle/settings APIs; those are isolated in the stacked
    #28792.
    - A TUI control or slash command for selecting the mode.
    - Persisting a preferred mode to `config.toml`; selection remains
    session/turn scoped.
    - Changes to multi-agent concurrency limits, tool availability, or model
    catalog capability declarations.
    - Rewriting historical rollout prompt items. Cold resume restores the
    latest persisted effective mode when available while leaving historical
    developer messages intact.
    
    ## Verification
    
    - `CARGO_INCREMENTAL=0 just test -p codex-core multi_agent_mode`
    - Focused app-server coverage verifies that `turn/start.multiAgentMode`
    produces proactive developer instructions for an eligible v2 turn.
    
    ## Stack
    
    Followed by #28792, which adds `thread/start` initialization and
    lifecycle/settings observability.
  • core: load AGENTS.md from foreign environments (#28958)
    ## Why
    
    Make it possible to load AGENTS.md from remote exec-servers whose OS is
    different than app-server.
    
    ## What
    
    - keep `AGENTS.md` discovery and provenance as `PathUri`, with
    root-aware parent and ancestor traversal
    - expose lifecycle instruction sources as legacy app-server path strings
    in events while retaining `PathUri` internally
    - preserve and test mixed POSIX and Windows paths in model context and
    TUI status output
    - cover remote Windows loading end to end by seeding the Wine prefix
    through host filesystem APIs
    - fix bug in `PathUri`'s parent() implementation that would erase
    Windows drive letters
  • feat: opt ChatGPT auth into agent identity (#19049)
    ## Stack
    
    This is PR 2 of the simplified HAI single-run-task stack:
    
    - [#19047](https://github.com/openai/codex/pull/19047) Agent Identity
    assertion and task-registration primitives, including the shared
    run-task helper used by existing Agent Identity JWT auth.
    - [#19049](https://github.com/openai/codex/pull/19049)
    Disabled-by-default ChatGPT auth opt-in that provisions/reuses persisted
    Agent Identity runtime auth and its single run task.
    - [#19051](https://github.com/openai/codex/pull/19051) Run-scoped
    provider auth that uses one backend-owned task id for first-party
    inference and compaction requests.
    
    [#19054](https://github.com/openai/codex/pull/19054) collapsed out of
    the active stack because the simplified design no longer needs a
    separate background/control-plane task helper.
    
    ## Summary
    
    This PR adds the disabled-by-default path for normal ChatGPT-login Codex
    sessions to obtain Agent Identity runtime auth through the Codex
    backend. Existing Agent Identity JWT startup mode remains a separate
    path and does not require the feature flag.
    
    What changed:
    
    - adds the experimental `use_agent_identity` feature flag and config
    schema entry
    - adds an explicit `AgentIdentityAuthPolicy` so call sites choose
    `JwtOnly` or `ChatGptAuth` instead of passing a bare boolean
    - stores standalone Agent Identity JWT credentials separately from
    backend-registered Agent Identity records
    - persists the registered Agent Identity record, private key, and single
    run task id in `auth.json` so process restarts reuse the same identity
    - derives the agent/task registration base URL from ChatGPT/Codex auth
    config while keeping JWT JWKS lookup separate
    - provisions and caches ChatGPT-derived Agent Identity runtime auth when
    `use_agent_identity` is enabled
    - reuses the shared run-task registration helper from PR1 rather than
    adding a second task-registration path
    
    This PR intentionally does not switch model inference over to
    `AgentAssertion` auth. The provider-auth integration lands in the next
    PR.
    
    ## Testing
    
    - `just test -p codex-login`
  • Emit Trusted MCP App Identity on Tool-Call Items (#27132)
    ## Summary
    
    - Add optional `appContext` to app-server MCP tool-call items with
    trusted `connectorId`, `linkId`, and `mcpAppResourceUri` metadata.
    - Preserve that context across tool-call events, persisted history,
    reconnects, and thread resume.
    - Keep the deprecated top-level `mcpAppResourceUri` temporarily for
    client migration.
    
    The consumer contract is `{ appContext: { connectorId, linkId,
    mcpAppResourceUri }, tool }`.
    
    ## Validation
    
    - Full GitHub Actions suite passes, including CLA, Bazel tests, clippy,
    release builds, and argument-comment lint.
    
    ---------
    
    Co-authored-by: martinauyeung-oai <280153141+martinauyeung-oai@users.noreply.github.com>
  • TUI: improve unified mention selection visibility (#28959)
    ## Summary
    
    [@milanglacier reported in
    #28653](https://github.com/openai/codex/issues/28653) that the active
    mention candidate is hard to distinguish. I suspect [@binbjz’s #28500
    report](https://github.com/openai/codex/issues/28500) _(where arrow-key
    navigation appeared not to work)_ may describe the same presentation
    problem: the selection may have been changing, but the UI was not
    showing the active row clearly in their terminal. This PR makes two
    small changes to the selection indication behavior:
    
    - Reserve a two-character gutter and mark the active candidate with `> `
    for color-agnostic indicator coverage.
    - Apply the shared theme-aware accent to the entire selected row for
    extra emphasis.
    - Update the existing popup snapshot.
    
    Reverse-video styling was considered, but avoided it because it is
    overly dependent on the user’s terminal palette.
    
    <img width="2046" height="482" alt="image"
    src="https://github.com/user-attachments/assets/b5eb62c3-fd24-4c09-906e-7bd66913b5c6"
    />
    
    ## Testing
    
    - `just test -p codex-tui default_unified_mention_popup_snapshot`
    - `just clippy -p codex-tui`
    - `just fmt`
    - Compiled `codex-cli` and tested the unified mentions picker in the
    terminal.
  • Add app-server current-time impl (varlatency 3/n) (#28835)
    ## What
    
    Server should request:
    
    ```
    {
      "id": 42,
      "method": "currentTime/read",
      "params": {
        "threadId": "11111111-1111-1111-1111-aaaaafdc2c11"
      }
    }
    ```
    
    Client should respond with something like:
    
    ```rust
    {
      "id": 42,
      "result": {
        "currentTimeAt": 1781717655
      }
    }
    ```
    
    ## Why
    
    Sessions configured with `clock_source = "external"` need a
    thread-specific external time source before inference. The system clock
    remains the default production provider.
    
    ## Validation
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server --test all
    current_time_read_round_trip_adds_reminder_to_model_input`
    - `cargo test -p codex-app-server
    first_attestation_capable_connection_for_thread_only_uses_thread_subscribers`
    - `cargo test -p codex-analytics`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    
    Stacked on #28824.
  • Support openai/form extended form elicitations (#27500)
    # Summary
    Allow App Server clients to opt into `openai/form` MCP elicitations.
  • Pause active goals before TUI interrupts (#28813)
    Fixes #28104.
    
    ## Summary
    Active `/goal` turns should leave the persisted goal paused whenever the
    TUI interrupts the running turn. The bug in #28104 showed this most
    visibly through `Esc`: some interrupt paths aborted the turn without
    updating the goal status, so the goal could remain active and continue
    automatically.
    
    This change makes `ChatWidget` pause an active goal before the TUI sends
    an interrupt from the status-row path, the pending-steer path, `Ctrl+C`,
    or a request-user-input overlay. The modal overlay now reports whether a
    key will interrupt the turn, which keeps modal `Esc` and `Ctrl+C`
    behavior aligned with the normal interrupt paths.
    
    ## Manual Testing
    Built the local CLI with `just codex --help`, then launched the local
    TUI with goals enabled. Started an active `/goal` turn and interrupted
    it with `Esc`, then resumed and repeated with `Ctrl+C`; both paths
    showed `Goal paused`, the interrupted-conversation message, and the
    `Goal paused (/goal resume)` footer. I also stopped the background
    terminal and exited the TUI cleanly after the run.
    
    I did not find a reliable standalone manual path to force the
    request-user-input overlay case, so that path is covered by the focused
    automated test.