Commit Graph

1349 Commits

  • [codex] Add optional IDs to response items (#28812)
    ## Why
    
    `ResponseItem` variants do not have a consistent internal ID shape: some
    variants carry required IDs, some carry optional IDs, and some cannot
    represent an ID at all. The existing fields also use inconsistent serde,
    TypeScript, and JSON-schema annotations. A single enum-level access path
    is needed before history recording can assign and retain IDs.
    
    This PR establishes that internal model only. It intentionally does not
    generate or serialize IDs; allocation and wire persistence are isolated
    in the stacked follow-up.
    
    ## What changed
    
    - Give every concrete `ResponseItem` variant an `Option<String>` ID
    field.
    - Apply the same internal-only annotations to every ID field:
    `#[serde(default, skip_serializing)]`, `#[ts(skip)]`, and
    `#[schemars(skip)]`.
    - Add `ResponseItem::id()` and `ResponseItem::set_id()` as the shared
    accessors.
    - Preserve IDs when history items are rewritten for truncation.
    - Adapt consumers that previously assumed reasoning and image-generation
    IDs were required.
    - Regenerate app-server schemas so the hidden fields are represented
    consistently.
    
    The serde catch-all `ResponseItem::Other` remains ID-less because it
    must remain a unit variant.
    
    ## Test plan
    
    - `cargo check --tests -p codex-core -p codex-api -p codex-rollout-trace
    -p codex-image-generation-extension`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-api -p codex-rollout-trace -p
    codex-image-generation-extension`
    - `just test -p codex-core event_mapping`
  • [codex] Track plugin install and import telemetry failures (#28731)
    ## Summary
    - Track plugin install failures through the unified
    `codex_plugin_install_failed` event for local installs, remote install
    preflight failures, bundle failures, and remote catalog/backend
    failures.
    - Send classified `error_type` values in plugin install failure
    analytics instead of raw error strings.
    - Stop sending raw external-agent import errors in analytics while
    preserving raw failure details in app-facing import
    notifications/history.
    - Keep raw plugin/migration diagnostics in `tracing::warn!` logs.
    - Keep remote failure plugin names as the existing local placeholder
    (`unknown`) and remove the extra telemetry plugin-name override.
    - Change `ExternalAgentConfigImportParams.source` from a generated enum
    to `string | null`, with legacy `claudeCode` / `claudeCowork` inputs
    normalized to existing analytics values.
    
    ## Testing
  • unified-exec: preserve PathUri through exec-server (#28681)
    ## Why
    
    It should be possible for app-server to handle "foreign" OS paths in
    unified_exec working directories, allowing e.g. a Linux app-server to
    run processes on e.g. a Windows exec-server.
    
    ## What
    
    Convert the core unified_exec cwd values to use `PathUri`.
    
    Adds fallible path conversion in several places to try to minimize the
    scope of this change. The only time this change suppresses errors from
    converting `PathUri` to an `AbsolutePathBuf` is when the turn is
    configured with no sandboxing at all to allow us to make progress
    testing without sandboxing.
    
    Future changes to apply_patch and sandboxing will clean up these error
    paths.
    
    A tool's cwd is resolved from joining a model-provided workdir to the
    environment's cwd. When using `AbsolutePathBuf::join()`, an
    absolute-path workdir would overwrite the environment's cwd and we would
    resolve permissions/sandboxing against the model-provided path. This
    change extends `PathUri::join()` to also treat an absolute rhs as an
    override of the base/lhs.
    
    This also removes some coverage from the remove_env_windows tests until
    a follow-up converts foreign paths in command exec events correctly.
    
    ## Breaking Changes
    
    When using `AbsolutePathBuf::join()` for workdir resolution, we ended up
    resolving tilde-prefixed paths against the app-server's `$HOME`, e.g.
    `~/foo/bar` becomes `/home/anp/foo/bar`. It's difficult to do this with
    `PathUri` joining, so after offline discussion this PR no longer
    implements it.
    
    A quick check of some power users' rollouts suggests that models don't
    actually generate home-prefixed absolute working directories for their
    spawns, so this shouldn't have any real blast radius.
  • [codex] Restore thread recency with compatible migration history (#28671)
    ## Summary
    
    - Revert #28655, restoring the thread `recencyAt` behavior introduced by
    #27910.
    - Move `threads_recency_at` to migration 0039 so it no longer collides
    with `external_agent_config_imports` at version 0038.
    - Repair databases that already applied the recency migration as version
    38 by moving the matching migration-history row to version 39 before
    SQLx validation. The current version-38 migration can then apply
    normally.
    
    ## Validation
    
    - `just test -p codex-state
    migrations::tests::repairs_recency_migration_that_was_applied_as_version_38`
    - `just test -p codex-state -p codex-rollout -p codex-thread-store -p
    codex-app-server-protocol -p codex-tui`: 3,439 passed; six TUI tests
    could not open the machine's existing read-only incident database at
    `~/.codex/sqlite/state_5.sqlite`.
    - `just fix -p codex-state`
    - `just fmt`
    - Verified that state migration versions are unique.
  • Scope command approvals by execution environment (#28738)
    ## Why
    
    Command approval cache keys included the command and working directory,
    but not the execution environment. An approval for `/workspace` locally
    could therefore be reused for the same command and path on an executor.
    
    ## What changed
    
    - Include the selected environment ID in shell and unified-exec approval
    cache keys.
    - Carry that ID through the normal command approval request so clients
    can show which environment is being approved.
    - Expose the environment through app-server as a required nullable
    `environmentId` and show it in the inline TUI approval prompt.
    - Keep older recorded approval events compatible when the environment is
    absent.
    
    For example, `echo ok` in local `/workspace` and `echo ok` in executor
    `/workspace` now produce different approval keys and separate prompts.
    
    ## Scope
    
    This PR does not change network approvals, Guardian review actions, MCP
    elicitation, full-screen TUI rendering, or environment-ID validation.
    Remote `shell_command` execution itself remains in #28722; this PR only
    makes its approval key environment-aware.
  • [ez][codex-rs] Support apps._default.default_tools_approval_mode (#27965)
    [from codex]
    
    ## Summary
    
    - add `default_tools_approval_mode` to `[apps._default]` and expose it
    through app-server v2 `config/read`
    - apply it after managed, per-tool, and per-app approval settings,
    before the built-in `auto` fallback
    - document the precedence, regenerate config/app-server schemas, and add
    unit plus end-to-end approval coverage
    
    ## Configuration
    
    ```toml
    [apps._default]
    default_tools_approval_mode = "prompt"
    ```
    
    The effective precedence is managed requirements, tool-specific
    `approval_mode`, app-specific `default_tools_approval_mode`,
    `apps._default.default_tools_approval_mode`, then `auto`.
    
    ## Test plan
    
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just write-app-server-schema --experimental`
    - `just test -p codex-core app_tool_policy`
    - `just test -p codex-core mcp_turn_metadata`
    - `just test -p codex-config`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server config_read_includes_apps`
    - `just fix -p codex-config -p codex-core -p codex-app-server-protocol
    -p codex-app-server`
    - `just fmt`
  • Replace SkillsManager with SkillsService (#28705)
    ## Why
    
    Host skill discovery was still exposed as a manager even though it is a
    process-owned service shared by sessions, the app-server catalog, and
    file-watcher invalidation. The skills extension also consumed an ad hoc
    loaded-skills wrapper instead of a named immutable snapshot.
    
    ## What changed
    
    - replace `SkillsManager` with concrete `SkillsService`
    - make the service cache and return immutable `HostSkillsSnapshot`
    values
    - migrate the skills extension host provider to the snapshot boundary
    - migrate app-server catalog, watcher, and invalidation paths to the
    service
    
    This keeps the service limited to host discovery, caching, roots, and
    invalidation. Catalog rendering and invocation remain extension
    responsibilities for the next stacked change.
  • app-server: keep the model cache warm (#28699)
    ## Why
    
    The app server is long-lived, but its shared model cache otherwise
    refreshes only when a caller needs it. Once the five-minute cache
    expires, starting a thread or calling `model/list` can wait for
    `/models` on the request path.
    
    Refresh the cache in the background before it expires so foreground
    callers normally use fresh local state.
    
    ## What changed
    
    - Start an app-server worker that refreshes models immediately and then
    every three minutes using the existing models-manager API.
    - Hold only a weak reference to the models manager between refreshes, so
    the worker does not extend its lifetime.
    - Stop scheduling refreshes when the app-server lifecycle handle is shut
    down or dropped. A refresh already in progress is allowed to finish.
    - Adjust affected app-server test fixtures to distinguish the background
    `/models` probe from the connection they are testing.
    
    The existing models-manager cache, refresh strategies, auth handling,
    ETag behavior, and concurrency semantics are unchanged.
    
    ## Testing
    
    -
    `models_refresh_worker::tests::refreshes_immediately_periodically_and_stops_when_dropped`
    -
    `suite::v2::remote_control::listen_off_honors_persisted_remote_control_enable`
    -
    `suite::v2::attestation::attestation_generate_round_trip_adds_header_to_responses_websocket_handshake`
  • Revert thread recencyAt for sidebar ordering (#28655)
    ## Why
    
    Revert #27910 to remove the newly introduced thread `recencyAt`
    persistence and API behavior from `main`.
    
    ## What changed
    
    This reverts commit `fac3158c2a783095768076489815f361fa9b0db4`,
    including the state migration, thread-store propagation, app-server API
    surface, generated schemas, and related tests.
    
    ## Validation
    
    Not run before opening; relying on CI for the initial fast signal.
  • Add thread recencyAt for sidebar ordering (#27910)
    ## Summary
    
    Add a server-owned `recencyAt` timestamp and `recency_at` thread-list
    sort key for product recency ordering while preserving the existing
    meaning of `updatedAt` as the latest persisted thread mutation.
    
    This is the server-side alternative to #27697. Rather than narrowing
    `updatedAt`, clients can sort the sidebar by `recency_at` and continue
    treating `updatedAt` as mutation time.
    
    Paired Codex Apps PR:
    [openai/openai#1024599](https://github.com/openai/openai/pull/1024599)
    
    ## Contract
    
    - `recencyAt` initializes when a thread is created.
    - A turn start advances `recencyAt` monotonically.
    - Commentary, agent output, tool results, token/accounting updates, turn
    completion, archive, unarchive, resume, and generic metadata writes do
    not advance it.
    - `updatedAt` retains its existing behavior and continues to advance for
    persisted thread mutations.
    - Current servers populate `recencyAt`; the response field is optional
    in generated TypeScript so clients connected to older servers can fall
    back to `updatedAt`.
    - Filesystem-only fallback uses existing updated/mtime ordering when
    SQLite is unavailable.
    
    ## Persistence and compatibility
    
    Migration 0038 adds second- and millisecond-precision recency columns,
    backfills them from the existing updated timestamp, creates list
    indexes, and includes an insert trigger so older binaries writing to a
    migrated database seed recency without causing later mutations to
    advance it.
    
    Generic metadata upserts preserve existing recency values. Turn-start
    updates use a dedicated monotonic touch, and process-local allocation
    keeps millisecond cursor values unique. State DB list, search, read,
    filtered-list repair, rollout fallback propagation, and app-server
    conversions all carry the new field.
    
    ## API
    
    `Thread` responses include:
    
    ```ts
    recencyAt?: number
    ```
    
    `thread/list` and `thread/search` accept:
    
    ```json
    { "sortKey": "recency_at" }
    ```
    
    Generated TypeScript and JSON schemas are included.
    
    ## Validation
    
    - `just test -p codex-state` — 146 passed
    - `just test -p codex-rollout` — 69 passed
    - `just test -p codex-thread-store` — 81 passed
    - `just test -p codex-app-server-protocol` — 231 passed
    - Focused app-server list ordering, response mapping, archive/unarchive,
    and resume lifecycle tests passed
    - Scoped `just fix` for state, rollout, thread-store,
    app-server-protocol, and app-server
    - `just fmt`
    - `git diff --check`
    - Independent correctness, simplicity, elegance, security, and
    test-quality reviews; actionable ordering, lifecycle, query-projection,
    and timestamp-uniqueness findings were addressed
  • [codex] [3/4] Activate endpoint plugin recommendations (#27704)
    Summary\n- Await endpoint recommendation selection while constructing
    each authenticated turn, removing the first-turn cache race.\n- Snapshot
    and filter endpoint candidates once per turn, then use that same set for
    the bounded contextual user fragment, tool exposure, and exact install
    validation.\n- Keep recommendation selection ephemeral: do not persist
    recommendation state in or gate resumed threads on prior context.\n-
    Hide the legacy list tool in endpoint mode and preserve legacy discovery
    unchanged when the endpoint is disabled or unavailable.\n- Keep remote
    plugin and connector app identities out of model-visible context and
    attach them only to Codex-owned elicitation metadata.\n\nStack\n- 3/4,
    based on #28400.\n- Endpoint client and cache: #28399.\n- Generalized
    suggestion presentation: #28400.\n- Install-schema follow-up:
    #28403.\n\nValidation\n- \n- \n- \n- \n- Full : 2,649 passed and 88
    environment-dependent tests failed because this sandbox cannot write ,
    nest Seatbelt, or locate auxiliary test binaries.
  • [codex] [1/4] Add recommended plugin endpoint cache (#28399)
    Summary
    - Add authenticated parsing for `/ps/plugins/suggested?scope=GLOBAL`,
    including remote plugin and connector app identities.
    - Validate, deduplicate, sort, and cap endpoint candidates before
    caching them by backend and account identity.
    - Deduplicate concurrent cache misses and warm recommendations from the
    existing remote-installed-plugin refresh path used at startup and after
    account changes.
    - Keep endpoint results model-invisible in this PR; failures and
    responses without `enabled: true` resolve to legacy mode.
    
    Stack
    - 1/3. Follow-up: #28400 generalizes plugin suggestion presentation
    without activating endpoint recommendations.
    - Final activation: #27704.
    
    Validation
    - `just test -p codex-core-plugins recommended_plugins`
    - `just fix -p codex-core-plugins`
    - `just fmt`
    - `git diff --check`
  • app-server: preserve target-native environment cwd (#28146)
    ## Why
    
    app-server may run on a different OS from the selected exec-server
    environment. Parsing that environment’s cwd with the Codex host’s path
    rules prevents thread startup.
    
    ## What
    
    Carry environment cwd values as `LegacyAppPathString` at the app-server
    boundary and `PathUri` internally. Existing tool-call schemas and
    relative-path behavior stay host-native; remaining local-only consumers
    convert explicitly and leave follow-up TODOs.
    
    The Wine integration test verifies app-server can start a thread and
    complete an ordinary turn with a Windows environment cwd from Linux.
    
    ## Validation
    
    - `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test
    --test_output=errors`
    - focused app-server environment-selection and protocol schema tests
    - scoped Clippy for `codex-core` and `codex-app-server-protocol`
  • [codex-app-server-test-client & codex-app-server] Plugin Usage Analytics Smoke Test (#27099)
    ## This PR
    
    The original [combined remote plugin analytics PR
    #26281](https://github.com/openai/codex/pull/26281) mixed reusable
    analytics test infrastructure, two manual smoke workflows, a metadata
    refactor, and the final identity behavior. This PR establishes a
    non-mutating end-to-end plugin smoke workflow before any analytics
    identity semantics change.
    
    - Add `plugin-analytics-smoke` to the existing app-server test client.
    - Exercise plugin disable, enable, and use through production app-server
    RPC paths.
    - Isolate config writes in a temporary file and use a loopback Responses
    API server.
    - Capture analytics without sending them to the production analytics
    backend.
    - Validate the current local `plugin_id`, names, capability metadata,
    thread, turn, and model fields.
    
    This is intentionally a baseline smoke workflow. It does not assert
    `remote_plugin_id`; the final PR will update it when that field exists.
    Review this PR as the net diff against #27093.
    
    ## Testing
    
    - The test-client target compiles successfully.
    - The combined reference branch exercised the manual smoke against the
    live remote plugin service.
    - CI is green across the required platform matrix.
    
    ## Split Overview
    
    ```text
    main
    ├── #27093  Debug analytics capture
    │   └── #27099  Non-mutating plugin smoke           ← you are here
    │       └── #27100  Remote install/uninstall smoke
    └── #27102  Plugin telemetry metadata refactor
    
    After #27093, #27099, #27100, and #27102 merge:
    └── Final PR: add remote_plugin_id to plugin analytics
    ```
    
    Review order and dependencies:
    
    1. [#27093 Add debug-only analytics event
    capture](https://github.com/openai/codex/pull/27093) (based on `main`)
    2. [#27099 Add a plugin analytics smoke
    workflow](https://github.com/openai/codex/pull/27099) **(this PR,
    stacked on #27093)**
    3. [#27100 Add a remote plugin analytics mutation smoke
    workflow](https://github.com/openai/codex/pull/27100) (stacked on this
    PR)
    4. [#27102 Centralize plugin telemetry metadata
    construction](https://github.com/openai/codex/pull/27102) (independent,
    based on `main`)
    5. Final remote-ID behavior PR (created after PRs 1-4 merge)
    
    The original [#26281](https://github.com/openai/codex/pull/26281)
    remains open as the green aggregate reference until the final PR is
    published.
  • [codex] exec-server: stream files in chunks (#28354)
    ## Why
    
    `fs/readFile` buffers the entire file in one response, which makes large
    remote reads expensive and prevents callers from applying backpressure.
    We need an opt-in streaming path with bounded block sizes while
    preserving the existing single-call API for small and sandboxed reads.
    
    ## What changed
    
    - Add `ExecServerClient::stream`, returning a named `FileReadStream`
    that implements `futures::Stream` and yields immutable 1 MiB byte
    blocks.
    - Add internal `fs/open`, `fs/readBlock`, and `fs/close` RPCs.
    `fs/readBlock` accepts an explicit offset and length.
    - Keep unsandboxed files open between block reads, cap open handles per
    connection, and clean them up on EOF, error, stream drop, explicit
    close, or connection shutdown.
    - Reject platform-sandboxed streaming opens instead of turning the
    one-shot sandbox helper into a persistent server. Existing `fs/readFile`
    behavior is unchanged.
    
    ## Testing
    
    - `just test -p codex-exec-server`
    - Integration coverage for 1 MiB chunking, exact block-boundary EOF,
    sandbox rejection, and continued reads from the opened file after path
    replacement.
    - Handle-manager coverage for non-sequential offsets, variable block
    lengths, the 128-handle limit, and capacity release after close.
  • [codex] expose Bedrock credential source in account/read (#27751)
    ## Why
    
    `account/read` currently reports only `type: "amazonBedrock"`, so
    clients cannot distinguish a Codex-managed Bedrock API key from
    credentials supplied by AWS. The app UI needs that distinction to render
    the appropriate account state without duplicating provider-auth logic.
    
    Credential-source selection belongs to the Bedrock model provider
    because it already owns the precedence between managed Bedrock auth and
    the external AWS credential path. This builds on #27443 and #27689.
    
    ## What changed
    
    - Added `AmazonBedrockCredentialSource` with `codexManaged` and
    `awsManaged` values.
    - Included the selected credential source in
    `ProviderAccount::AmazonBedrock` and the app-server `Account` response.
    - Made `AmazonBedrockModelProvider::account_state()` classify the source
    from its managed-auth state.
    - Regenerated the app-server JSON and TypeScript schemas.
    - Updated app-server account documentation and downstream TUI matches.
    
    `codexManaged` means the provider found a managed Bedrock API key.
    `awsManaged` identifies the provider's external AWS credential path; it
    does not assert that the AWS credential chain has been validated.
    
    ## Testing
    
    - Added model-provider coverage for Codex-managed precedence and
    AWS-managed fallback.
    - Added app-server protocol serialization coverage for both wire values.
    - Added app-server integration coverage for both `account/read`
    responses.
    - `just test -p codex-protocol -p codex-model-provider -p
    codex-app-server-protocol` (497 tests passed).
    
    After rebasing onto #27711, the `codex-app-server` test target compiled
    past the image-generation `PathUri` migration. Local linking was then
    interrupted by disk exhaustion (`No space left on device`).
  • [codex] Record external agent import results (#28396)
    ## Summary
    - restore `externalAgentConfig/import/progress` notifications while
    keeping `externalAgentConfig/import/completed` as the must-deliver event
    - persist completed external-agent config imports in state DB by
    `importId`, including concrete success/failure details for config,
    AGENTS.md, skills, plugins, MCP servers, subagents, hooks, commands, and
    sessions
    - add `externalAgentConfig/import/readHistories` so clients can recover
    persisted import results after missing the live completion notification
    - include `errorType` on import failures in protocol
    responses/notifications and persisted DB JSON so future code can
    classify failures without another wire/storage shape change
    
    ## Validation
    - `git diff --check`
    - `just test -p codex-state external_agent_config_imports`
    - `just test -p codex-app-server-protocol`
    - `CODEX_SQLITE_HOME=/private/tmp/codex-app-server-sqlite-read-details
    just test -p codex-app-server
    external_agent_config_import_sends_completion_notification_for_sync_only_import`
    
    Also ran earlier broader checks before publishing:
    - `just test -p codex-state`
    -
    `CODEX_SQLITE_HOME=/private/tmp/codex-app-server-external-agent-test-sqlite
    just test -p codex-app-server external_agent_config`
    - `just test -p codex-external-agent-migration`
  • [codex] Use expect in integration tests (#28441)
    The workspace denies `clippy::expect_used` in production. Although
    `clippy.toml` allows `expect` in tests, Bazel Clippy compiles
    integration-test helper code in a way that does not receive that
    exemption, which encouraged verbose `unwrap_or_else(... panic!(...))`
    and equivalent `match`/`let else` forms.
    
    This allows `clippy::expect_used` once at each integration-test crate
    root (including aggregated suites and test-support libraries), then
    replaces manual panic-based Result and Option unwraps with
    `expect`/`expect_err`. Standalone `tests/*.rs` files remain their own
    crate roots. Intentional assertion and unexpected-variant panics remain
    unchanged, and the production `expect_used = "deny"` lint remains in
    place.
    
    The cleanup is mechanical and net-negative in line count.
  • [codex] Add interruptible sleep tool (#28429)
    ## Why
    
    Models sometimes need to pause briefly while waiting for external work,
    but using a shell command for that delay ties the wait to a process and
    does not naturally resume when new turn input arrives.
    
    ## What changed
    
    - add a built-in `sleep` tool behind the under-development `sleep_tool`
    feature
    - accept a bounded `duration_ms` argument, matching the millisecond
    convention used by unified exec
    - end the sleep early when either steered user input or mailbox input
    arrives
    - include elapsed wall-clock time in completed and interrupted outputs
    - emit a dedicated core `SleepItem` through `item/started` and
    `item/completed`
    - expose the sleep item as app-server v2 `ThreadItem::Sleep` and retain
    it in reconstructed thread history
    - regenerate the configuration schema for the new feature flag
    - regenerate app-server JSON and TypeScript schema fixtures
    
    ## Test plan
    
    - `just test -p codex-core sleep_tool_follows_feature_gate`
    - `just test -p codex-core any_new_input_interrupts_sleep`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    sleep_emits_started_and_completed_items`
  • Use ApiPathString in app-server filesystem permission paths (#28367)
    ## Why
    
    Clients running an app-server on one OS and an exec-server on another OS
    need to be able to pass sandbox config to app-server that refers to
    resources on the executor's foreign OS.
    
    ## What
    
    `AbsolutePathBuf` can't represent these paths and we don't want users to
    be exposed to `PathUri` yet, so this moves the public app-server API to
    be expressed in terms of `ApiPathString`.
    
    Stacked on #28165.
    
    - change app-server v2 filesystem permission paths, including legacy
    read/write roots, to `ApiPathString`
    - localize API paths through `PathUri` when converting into the current
    native core permission types
    - make path-bearing permission conversions fallible and surface
    localization failures instead of silently treating malformed grants as
    ordinary denials
    - propagate conversion failures through app-server and TUI approval
    handling
    - regenerate the app-server JSON and TypeScript schemas
    - leave migration TODOs on native-path conversions so they can be
    removed once core permission paths use `PathUri`
  • [codex] Make plugin details capability aware (#27958)
    ## Summary
    
    Makes plugin details/read flows capability-aware so auth-filtered plugin
    surfaces report the same usable app/MCP/skill shape as the marketplace
    and install flows.
    
    ## Validation
    
    Not run; this change was rebased onto the current plugin auth stack and
    pushed as a draft PR.
    
    **Manual test**
    1. set up a local marketplace with a plugin that has both app and mcp
    declarations
    
    ```
    // .app.json
    {
      "apps": {
        "linear": {
          "id": "some_id"
        }
      }
    }
    
    ```
    
    ```
    // .mcp.json
    {
      "mcpServers": {
        "linear": {
          "type": "http",
          "url": "https://mcp.linear.app/mcp",
          "oauth_resource": "https://mcp.linear.app/mcp"
        },
        "linear2": {
          "type": "http",
          "url": "https://mcp.linear2.app/mcp",
          "oauth_resource": "https://mcp.linear2.app/mcp"
        }
      }
    }
    ```
    
    2a. **login in with api key** and observe plugin details page which
    shows no apps (note we don't show "app not available due to api key log
    in as there's no way to differentiate between no apps and app without
    substitute mcp exists" without significantly more code changes, i've
    separated this to a follow up if we want that behaviour.
    <img width="1170" height="279" alt="Screenshot 2026-06-15 at 23 45 40"
    src="https://github.com/user-attachments/assets/d36cb160-fbec-461e-9643-9c761dbae7bb"
    />
    <img width="975" height="640" alt="Screenshot 2026-06-15 at 18 40 30"
    src="https://github.com/user-attachments/assets/90ec0bc8-7506-4b90-bbd3-070720de799e"
    />
    
    
    2b. **log in with chat** and observe intended conflict resolution logic
    <img width="1165" height="224" alt="Screenshot 2026-06-15 at 17 17 30"
    src="https://github.com/user-attachments/assets/80adfbf2-7dac-4f08-8b76-8eeeab6c95e7"
    />
    <img width="968" height="567" alt="Screenshot 2026-06-15 at 18 38 59"
    src="https://github.com/user-attachments/assets/9ea92c5e-535b-4aa4-8ad0-ee513b57bc3c"
    />
  • [codex] Load API curated marketplace by auth (#28383)
    ## Summary
    - choose the local OpenAI curated marketplace manifest based on auth:
    Codex backend auth gets the existing marketplace, direct provider auth
    gets `api_marketplace.json`
    - include Bedrock API key auth in the direct-provider API marketplace
    path
    - safely skip the API marketplace when `api_marketplace.json` is absent
    
    ## Validation
    - `just fmt`
    - `git diff --check origin/main...HEAD`
    - CI should run the full validation
    
    ## Manual Testing
    
    ### - New api marketplace not available for API key sign
    1. Safely not display anything from api marketplace
    <img width="1161" height="289" alt="Screenshot 2026-06-15 at 21 37 43"
    src="https://github.com/user-attachments/assets/a5f16642-8a20-4ac1-a0de-1274a4c7b5b2"
    />
    
    ### - New api marketplace for API key sign in
    1. Setup api_marketplace.json
    ```
    {
      "name": "openai-curated",
      "interface": {
        "displayName": "Codex official"
      },
      "plugins": [
        {
          "name": "linear",
          "source": {
            "source": "local",
            "path": "./plugins/linear"
          },
          "policy": {
            "installation": "AVAILABLE",
            "authentication": "ON_INSTALL"
          },
          "category": "Productivity"
        }
      ]
    }
    ```
    
    2. Log in with API key, observe that only the defined plugin from
    api_marketplace.json is available from "Codex Official" (outside of
    local testing marketplaces)
    <img width="1167" height="446" alt="Screenshot 2026-06-15 at 21 16 53"
    src="https://github.com/user-attachments/assets/7cf61477-d826-4ef6-bc05-0a23ac1c0259"
    />
    
    also checked functionality on codex app
    
    ### - SiWC users 
    Still uses 'default' marketplace.json and renders all plugins
    <img width="1171" height="502" alt="Screenshot 2026-06-15 at 21 40 25"
    src="https://github.com/user-attachments/assets/d212ea9b-0aa5-470b-8ea4-450efe65bb2b"
    />
    
    also checked functionality on codex app
    
    
    ## Notes
    - `just test -p codex-core-plugins` was started locally before splitting
    branches, but I stopped relying on local tests per follow-up and left
    final validation to PR CI.
  • Add a toggle for realtime startup context (#28405)
    ## Summary
    - Add `includeStartupContext` to realtime start requests so callers can
    explicitly skip Codex startup context while keeping the backend prompt
    - Thread the new flag through protocol types, request processing, and
    realtime session config
    - Update app-server docs and coverage for the new default and opt-out
    behavior
    
    ## Testing
    - Added protocol serialization coverage for `includeStartupContext`
    - Added realtime integration coverage for starting a session with
    startup context disabled
  • Add realtime speech append control (#27917)
    ## Why
    
    Realtime voice harness tuning needs app-side control over what backend
    Codex text is spoken. Backend orchestrator text is written for a reading
    UI, so automatically speaking every preamble, progress update, or final
    assistant message can make the realtime voice model too chatty.
    
    For experimentation, clients need two simple controls: keep app/client
    text-item injection on the existing item-create path, and add an
    explicit speakable path that app code can call only when it wants
    realtime to speak. Automatic Codex output also needs an opt-in way to
    switch from the protocol's default speakable path to regular realtime
    items, with a caller-provided prefix so prompt wording can be tuned
    outside core.
    
    The default remains unchanged: if a client omits the new start fields
    and never calls `appendSpeech`, automatic backend output continues down
    the existing speakable path for the selected realtime protocol.
    
    ## What Changed
    
    - Adds experimental `thread/realtime/appendSpeech` for app-provided
    speakable text.
    - Keeps existing `thread/realtime/appendText` as the item-create API for
    app-provided realtime text items.
    - Adds `codexResponsesAsItems` / `codex_responses_as_items` on
    `thread/realtime/start` to send automatic Codex responses with
    `conversation.item.create` instead of the protocol's default speakable
    output path.
    - Adds `codexResponseItemPrefix` / `codex_response_item_prefix` so
    clients can prepend experiment instructions to those automatic Codex
    response items.
    - Keeps literal `conversation.handoff.append` routing scoped to the v1
    speakable path; v2 default speech uses its item/function-output plus
    `response.create` behavior.
    - Removes the earlier public silent-context API and hardcoded
    silent-context prefix.
    - Updates realtime tests to cover default automatic speakable behavior,
    opt-in automatic item-create behavior, and explicit `appendSpeech`
    behavior.
    
    ## Validation
    
    - `cargo check -p codex-core -p codex-app-server -p codex-api`
    - `just test -p codex-app-server realtime_conversation`
    - `just test -p codex-core realtime_conversation` (50/51 passed in the
    filtered parallel run; the lone failure passed when rerun in isolation)
    - `just test -p codex-core
    conversation_mirrors_assistant_message_text_to_realtime_handoff`
    - `just test -p codex-api
    e2e_connect_and_exchange_events_against_mock_ws_server`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
    - `cargo build -p codex-cli`
  • [codex] Add created-by-me remote plugin marketplace (#28203)
    ## Summary
    - add the `created-by-me-remote` marketplace backed by paginated
    `scope=USER` plugin directory and installed-plugin requests
    - include USER plugins in installed-plugin caching, bundle sync, and
    stale-cache cleanup without client-side discoverability filtering
    - expose the marketplace through app-server v2 and regenerate the
    protocol schemas
    
    ## Testing
    - `cargo build -p codex-app-server --bin codex-app-server`
    - production-auth `plugin/list` smoke test for `created-by-me-remote`
    (returned the expected USER plugin as installed and enabled)
    - `just test -p codex-core-plugins` (221 passed)
    - `just test -p codex-app-server-protocol` (231 passed)
    - `just test -p codex-app-server suite::v2::plugin_list::` (37 passed)
    - `just fix -p codex-core-plugins -p codex-app-server-protocol -p
    codex-app-server`
    - `just fmt`
  • feat(core): add metadata field to ResponseItem (#28355)
    ## Description
    
    This PR adds an optional `metadata` field to `ResponseItem` for
    Responses API calls. Only mechanical plumbing, no actual values
    populated and sent yet. Turns out just adding a new field to
    `ResponseItem` has quite a large blast radius already.
    
    This change is backwards compatible because `metadata` is optional and
    omitted when absent, so existing response items and rollout history
    without it still deserialize and requests that do not set it keep the
    same wire shape. For provider compatibility, we strip out `metadata`
    before non-OpenAI Responses requests so Azure and AWS Bedrock never see
    this field.
    
    My followup PR here will actually make use of it to start storing and
    passing along `turn_id`: https://github.com/openai/codex/pull/28360
    
    ## What changed
    
    - Added `ResponseItemMetadata` with optional `turn_id`, plus optional
    `metadata` on Responses API item variants and inter-agent communication.
    - Preserved item metadata through response-item rewrites such as
    truncation, missing tool-output synthesis, compaction history
    rebuilding, visible-history conversion, rollout/resume, and generated
    app-server schemas/types.
    - Strip item metadata from non-OpenAI Responses requests while
    preserving it for OpenAI-shaped requests.
    - Updated the mechanical fixture/test construction churn required by the
    new optional field.
  • feat(app-server): expose rate-limit reset credits (#28143)
    ## Why
    
    Codex users can earn personal rate-limit reset credits, but app-server
    clients do not currently have an API for reading or redeeming them. This
    adds the backend and protocol foundation used by the `/usage` TUI flow
    in #28154.
    
    ## What changed
    
    - Extend `account/rateLimits/read` with a nullable
    `rateLimitResetCredits` summary sourced from the existing usage
    response.
    - Add backend-client and app-server support for consuming a reset with a
    caller-generated idempotency key. A UUID is recommended, and clients
    reuse the same key when retrying the same logical reset.
    - Return only the consume `outcome`; clients refetch
    `account/rateLimits/read` for updated window state.
    - Document the response field and each consume outcome, and regenerate
    the JSON and TypeScript schema fixtures.
    - Clarify in `AGENTS.md` that new app-server string enum values use
    camelCase on the wire.
    - Update the existing TUI response fixture for the expanded protocol
    shape.
    - Add coverage for authentication, response mapping, backend failures,
    consume outcomes, and request timeout behavior.
    
    ## Validation
    
    - `just test -p codex-app-server-protocol` — 231 passed.
    - `just test -p codex-backend-client` — 14 passed.
    - Focused `codex-app-server` reset-credit tests — 5 passed.
    - Focused `codex-tui` protocol response fixture test — passed.
    - `just fix -p codex-backend-client -p codex-app-server-protocol -p
    codex-app-server` — passed.
    - `just fmt` — passed.
  • [codex] Add external agent import result accounting (#28008)
    ## Why
    
    External-agent imports can complete synchronously or continue in the
    background for plugins/sessions. Clients need a stable import id to
    correlate the immediate response with the eventual completion
    notification, and the completion payload needs enough accounting to show
    which artifact types succeeded or failed without hiding partial
    failures.
    
    ## What Changed
    
    - `externalAgentConfig/import` now returns an `importId`;
    `externalAgentConfig/import/completed` includes the same `importId` plus
    type-level `itemResults`.
    - Completed `itemResults` report `successCount`, `errorCount`,
    `successes`, and `rawErrors` for each migrated item type.
    - Added protocol/schema/TypeScript types for import successes, raw
    errors, and type-level results. No progress notification is included in
    the final PR.
    - `ExternalAgentConfigService::import` now returns an outcome object
    with synchronous item results and pending plugin imports.
    - Plugin import outcomes track succeeded/failed marketplaces, plugin
    ids, and raw errors. Plugin failures can be reported in completed
    accounting while later migration items continue.
    - Non-plugin synchronous import failures still fail the request, so
    invalid config/skills-style failures are not reported as a successful
    import response.
    - Session imports now return item results. Successful imports include
    the source session path and imported thread id; prepare, persist,
    ledger, and source-validation failures become raw errors in completion
    accounting where the import can continue.
    - The request processor generates the `importId`, aggregates synchronous
    results with background plugin/session results, and sends a single
    completed notification when all selected work is done.
    - App-server docs and generated schema fixtures were updated for the new
    response/completed payload shapes.
    
    ## Validation
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server-client event_requires_delivery`
    - `CODEX_SQLITE_HOME=/private/tmp/codex-app-server-review-sync-error
    just test -p codex-app-server
    external_agent_config_import_returns_error_for_failed_sync_import`
    - `CODEX_SQLITE_HOME=/private/tmp/codex-app-server-review-external-agent
    just test -p codex-app-server external_agent_config`
    
    Note: local sandbox validation used `CODEX_SQLITE_HOME` because the
    default sqlite state path is read-only in this environment.
  • skills: cache orchestrator resources per thread (#28336)
    ## Why
    
    Hosted orchestrator skills are read through the remote MCP resource
    server. Within one thread, the same catalog or skill resource can be
    requested multiple times by prompt injection and the `skills.list` /
    `skills.read` tools. Re-fetching adds latency and can make those
    surfaces observe different remote contents during the same thread.
    
    This is a follow-up to #28333: orchestrator skills remain limited to
    threads without a local executor, and those threads now get a stable
    per-thread view of the remote skill data they use.
    
    ## What changed
    
    - Reuse the existing per-thread orchestrator catalog snapshot for
    `skills.list` and `skills.read` availability checks.
    - Cache successful orchestrator resource reads by authority, package,
    and resource so prompt injection and tool calls share the same contents.
    - Keep the cache memory-only and bounded to 100 resources and 8 MiB per
    thread.
    - Leave host and executor skill reads unchanged, and do not cache failed
    remote reads.
    
    ## Verification
    
    - Extended the app-server MCP resource integration test to read the same
    hosted skill resource twice and verify that the remote server receives
    one read.
    - The same test verifies that catalog discovery and the selected skill's
    main prompt are each fetched only once per thread.
  • Support staging OAuth client ID overrides (#28257)
    ## Summary
    
    - allow app-server ChatGPT login to use a configured OAuth client ID
    - reuse the same client ID for refresh and revoke requests
    - cover staging login, refresh, and revoke request payloads
    
    ## Tests
    
    - `just test -p codex-login`
    - `just test -p codex-app-server
    login_account_chatgpt_uses_debug_oauth_overrides`
    - `just test -p codex-login
    logout_with_revoke_revokes_refresh_token_then_removes_auth`
    - `just fix -p codex-login`
    - `just fix -p codex-app-server`
    - `just fmt`
  • TUI Plugin Sharing 2 - add remote plugin section plumbing (#26702)
    This adds the background plumbing for remote-backed plugin catalog
    sections while leaving the fuller directory presentation to the next PR.
    The TUI can fetch section-specific remote marketplace results, keep
    local plugin data available, and carry section errors forward for later
    rendering.
    
    - Fetches explicit remote marketplace kinds for curated, workspace, and
    shared-with-me sections.
    - Gates shared-with-me loading on the plugin sharing feature flag.
    - Adds section-level error state and user-actionable error copy.
    - Merges remote marketplace results into the cached plugin list without
    discarding local results.
  • [codex] preserve explicit environment cwd (#27995)
    ## Why
    
    `TurnEnvironmentSelections::new` rewrote the primary environment's
    explicit `cwd` to the legacy fallback cwd. For a remote-first selection,
    this could replace the remote working directory with a local fallback
    path and made the legacy cwd overlay authoritative over
    environment-owned state.
    
    ## What changed
    
    - Preserve every explicit environment cwd when constructing turn
    environment selections.
    - Keep `cwd`-only app-server updates compatible by rebuilding the
    default environment selections at the requested cwd.
    - Cover both explicit primary cwd preservation and cwd-only updates
    reaching the model-visible execution environment.
    
    ## Testing
    
    - `just test -p codex-core
    session_update_settings_does_not_rewrite_sticky_environment_cwds`
    - `just test -p codex-core
    environment_settings_preserve_explicit_primary_cwd`
    - `just test -p codex-app-server
    thread_settings_update_cwd_retargets_default_environment`
  • Expose explicit dynamic tool namespaces in thread start (#27371)
    Stacked on #27365.
    
    ## Stack note
    
    [#27365](https://github.com/openai/codex/pull/27365) kept `thread/start`
    unchanged and converted its input in `thread_processor`. This PR updates
    `thread/start` to accept explicit functions and namespaces directly.
    
    Legacy per-tool arrays are still accepted and converted while reading
    the request. As a result, `thread_processor` can validate and pass the
    tools through directly, which is why some code added in #27365 is
    removed here.
    
    ## Why
    
    `thread/start.dynamicTools` still repeats namespace data on each
    function even though core now stores explicit namespace groups. The
    request API should use the same shape so each namespace has one
    description and one member list.
    
    ## What changed
    
    - Accept top-level functions and explicit namespace objects in
    `dynamicTools`.
    - Continue accepting fully legacy flat arrays, including
    `exposeToContext`.
    - Reject arrays that mix legacy and canonical entries.
    - Reuse the protocol types directly and remove the temporary app-server
    adapter.
    - Update validation, docs, the test client, and generated schemas.
    
    ## Test plan
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    dynamic_tool_call_round_trip_sends_text_content_items_to_model`
    - `just test -p codex-app-server
    thread_start_normalizes_legacy_dynamic_tools_into_model_request`
    - `just test -p codex-app-server
    thread_start_rejects_mixed_dynamic_tool_formats`
    - `just test -p codex-app-server
    thread_start_rejects_hidden_dynamic_tools_without_namespace`
  • skills: hide orchestrator skills with a local executor (#28333)
    ## Why
    
    App-server threads without a local executor need orchestrator-owned
    skills from the hosted `codex_apps` MCP server. Threads with the local
    executor already discover installed skills from the local filesystem.
    
    After the orchestrator skill provider was enabled for every app-server
    thread, local-executor threads also received the hosted skill catalog
    and the `skills.list` and `skills.read` tools. This changed the existing
    local behavior and could expose a second hosted copy of a skill that was
    already installed locally.
    
    ## What changed
    
    - Expose the thread's selected execution environments to extensions at
    thread startup.
    - Enable orchestrator skills only when the reserved local environment is
    not selected.
    - Apply that decision consistently to hosted skill catalog discovery,
    explicit skill injection, and the `skills.list` and `skills.read` tools.
    
    ## Verification
    
    - The existing no-executor app-server test continues to verify hosted
    skill discovery, invocation, and child-resource reads.
    - A new app-server test verifies that local-executor threads do not
    receive hosted skill context or `skills.*` tools.
  • Represent dynamic tools with explicit namespaces internally (#27365)
    Follow-up to #27356.
    
    ## Stack note
    
    This PR changes Codex's internal dynamic-tool shape while leaving
    `thread/start` unchanged. App-server therefore converts the existing
    per-tool input into explicit functions and namespaces before passing it
    to core.
    
    [#27371](https://github.com/openai/codex/pull/27371) updates
    `thread/start` to use the same explicit shape and removes this temporary
    conversion.
    
    ## Why
    
    Dynamic tools repeat namespace metadata on every function. Core should
    keep one explicit namespace with its member tools so descriptions and
    membership stay consistent across sessions and runtime planning.
    
    ## What changed
    
    - Represent dynamic tools as top-level functions or explicit namespaces
    in protocol and session state.
    - Read old flat rollout metadata and write the canonical hierarchy.
    - Flatten namespace members only when registering callable tools.
    - Keep `thread/start.dynamicTools` flat for now and normalize it at the
    app-server boundary.
    
    New builds can read old rollout metadata. Older builds cannot read newly
    written hierarchical metadata.
    
    ## Test plan
    
    - `just test -p codex-app-server
    thread_start_normalizes_legacy_dynamic_tools_into_model_request`
    - `just test -p codex-protocol
    session_meta_normalizes_legacy_dynamic_tools`
    - `just test -p codex-core
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
    - `just test -p codex-core
    tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call`
    - `just test -p codex-core code_mode_can_call_hidden_dynamic_tools`
    - `just test -p codex-tools`
  • [codex] Cap feedback upload subtrees (#28332)
    ## Summary
    - cap feedback log uploads to at most eight threads before SQLite log
    aggregation and rollout attachment resolution
    - keep the root session included while bounding descendant fanout during
    `/feedback` uploads
    
    ## Why
    Very large sessions can accumulate large spawned-thread subtrees.
    Feedback uploads currently walk the entire subtree and then read each
    resolved rollout into memory, which can blow up when one session has
    hundreds of descendants.
    
    ## Validation
    - ran `just fmt`
    - did not run tests or Clippy per request; CI will cover validation
  • Activate selected executor plugin MCPs in app-server (#27893)
    ## Why
    
    #27870 teaches the MCP extension how to discover stdio MCP servers
    declared by a selected executor plugin, but app-server does not yet
    install that contributor or initialize its per-thread state. As a
    result, `thread/start.selectedCapabilityRoots` can select the plugin
    while its MCP servers remain inactive.
    
    This PR closes that app-server wiring gap:
    
    ```text
    thread/start(selectedCapabilityRoots)
        -> initialize the thread's selected-plugin MCP snapshot
        -> read the selected plugin's .mcp.json through its environment
        -> start declared stdio servers in that environment
        -> expose their tools only on the selected thread
    ```
    
    ## What changed
    
    - Install the selected-executor-plugin MCP contributor in app-server
    using the existing shared `EnvironmentManager`.
    - Initialize its frozen thread snapshot when `thread/start` includes
    selected capability roots.
    - Document that selected plugin stdio MCPs are activated in their owning
    environment.
    - Add an app-server E2E covering the complete selection-to-tool-call
    path.
    
    The E2E verifies that:
    
    - the selected MCP process receives an executor-only environment value,
    proving the tool runs through the selected environment;
    - the MCP tool is advertised to the model and can be called;
    - a normal MCP config reload does not discard the thread's frozen
    selected-plugin registration;
    - another thread without the selected root does not see the MCP server.
    
    ## Scope
    
    - Existing sessions without `selectedCapabilityRoots` are unchanged.
    - Only stdio MCP declarations are activated. HTTP declarations remain
    inactive.
    - This does not change selected-root persistence across resume/fork or
    add hosted-plugin behavior.
    
    ## Verification
    
    - Focused app-server E2E:
    `selected_executor_plugin_exposes_its_stdio_mcp_only_to_that_thread`
    
    ## Stack
    
    Stacked on #27870.
  • [codex] Skip plugin MCP OAuth for matching app routes (#27461)
    ## Context
    
    This is PR5 in the plugin auth-routing stack. Earlier PRs make plugin
    surface projection auth-aware, narrow App/MCP conflicts by App
    declaration name, and keep connector listings auth-aware. This PR
    applies the same name-based App/MCP conflict rule into plugin MCP
    loading, so install-time MCP OAuth and plugin detail metadata both
    reflect the MCPs available for the current auth route.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Make `load_plugin_mcp_servers` auth-aware and let it load App
    declarations before filtering same-name MCP servers for Codex-backend
    auth.
    - Use that filtered MCP list for both install-time MCP OAuth and
    marketplace plugin detail metadata.
    - Preserve API-key/direct auth behavior so plugin MCP servers remain
    visible and can still start OAuth.
    
    ## Validation
    
    ```bash
    cargo fmt --all
    cargo test -p codex-core-plugins read_plugin_for_config_filters_mcp_servers_for_codex_backend_auth
    cargo check -p codex-core-plugins -p codex-app-server
    git diff --check
    git diff --cached --check
    ```
  • [codex] Preserve plugin apps in connector listings (#27602)
    ## Context
    
    This is PR4 in the plugin auth-routing stack. The earlier PRs make
    plugin surface projection auth-aware and narrow App/MCP conflicts by App
    declaration name. This PR keeps connector listing paths aligned with
    that projected plugin App set.
    
    This means ChatGPT/SIWC users will still see plugin-provided Apps in
    connector listing surfaces like the Apps/connector picker, while API-key
    users will not see Apps they cannot use.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Have app-server compute effective plugin Apps from the existing
    PluginsManager and pass them into connector listing.
    - Keep plugin Apps visible in Apps/connector listing for ChatGPT/SIWC
    users.
    - Keep API-key-style auth from surfacing plugin Apps in connector
    listings.
    
    ## Validation
    
    ```bash
    cargo test -p codex-chatgpt connectors::tests
    cargo test -p codex-app-server list_apps_includes_plugin_apps_for_chatgpt_auth
    git diff --check
    ```
  • feat(app-server): filter threads by parent (#26662)
    ## Why
    
    Clients that display or coordinate spawned subagents need an
    authoritative snapshot of a thread's immediate spawned children when
    they connect to app-server or recover after missing live events.
    `thread/list` cannot query by parent, so clients must otherwise scan
    unrelated threads or reconstruct relationships from rollout history and
    transient events.
    
    The direct spawn relationship already exists in persisted
    `thread_spawn_edges` state. Review and Guardian threads do not
    participate in that lifecycle and are intentionally outside this
    filter's scope.
    
    ## What changed
    
    This adds an experimental `parentThreadId` filter to `thread/list`.
    Parent-filtered requests return direct spawned children from persisted
    state while preserving the existing response shape, explicit filters,
    sorting, and timestamp-only cursor behavior. The lookup does not read
    rollout transcripts or recursively return descendants.
    
    Supersedes #25112 with the narrower `thread/list` filter approach.
    
    ## How it works
    
    1. An experimental client passes a valid thread ID as `parentThreadId`.
    2. App-server routes the list through the existing thread-store and
    state-database boundaries.
    3. SQLite selects threads whose IDs have a direct persisted spawn edge
    from that parent.
    4. Omitted provider and source filters include all values; explicit
    filters keep ordinary `thread/list` semantics.
    5. Grandchildren, Review threads, and Guardian threads are excluded.
    
    ## Verification
    
    State (144 tests), rollout (69 tests), and focused app-server
    thread-list (31 tests) suites passed. Scoped Clippy checks and
    repository formatting also passed. Coverage includes direct spawned
    children, omitted grandchildren, pagination, malformed IDs, mixed source
    kinds, explicit filters, and operation without rollout files.
  • [codex] exec-server honors remote environment cwd and shell (#28122)
    ## Why
    
    Next slice needed to make progress on the `remote_env_windows` test is
    to support passing a Windows cwd for the remote environment and using
    that environment's native shell. This lets the test run a real Windows
    process instead of only recording an early path or shell mismatch.
    
    ## What
    
    - change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
    `PathUri`
    - convert local cwd values to URIs when constructing selections
    - preserve a remote primary cwd instead of replacing it with the local
    legacy fallback
    - prefer the selected environment's discovered shell for unified exec,
    falling back to the session shell when unavailable
    - convert back to a host-native absolute path at current native-only
    consumer boundaries
    - reject or deny unsupported foreign cwd values at the existing
    request-permissions boundary, with TODOs for its future migration
    - extend the hermetic Wine test to execute Windows PowerShell in
    `C:\windows` and verify successful process completion
    - record the current app-server rejection against the same Wine-backed
    remote Windows fixture when its cwd is supplied as a native Windows path
  • build: run buildifier from just fmt (#28125)
    ## Intent
    
    Keep Bazel and Starlark files consistently formatted without requiring
    contributors to install or version buildifier themselves.
    
    ## Implementation
    
    - Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
    v8.5.1.
    - Run buildifier from the shared `just fmt` and `just fmt-check` driver,
    with Windows-safe explicit DotSlash invocation.
    - Provision DotSlash in formatting CI and contributor devcontainers, and
    document the source-build prerequisite.
    - Apply the initial mechanical buildifier formatting baseline.
  • [codex] Dedupe plugin MCPs by app declaration name (#27607)
    ## Context
    
    This is the next step in the plugin auth-routing stack. The earlier PRs
    make `PluginsManager` auth-aware and move the broad App/MCP surface
    decision into that layer. This PR narrows the ChatGPT/SIWC behavior so
    we only hide a plugin MCP server when it conflicts with an App
    declaration of the same name.
    
    In product terms: if a plugin exposes both an App route and MCP route
    for `foo`, ChatGPT/SIWC sessions should use the App route for `foo`. If
    the same plugin also exposes a separate MCP server like `foo2`, that MCP
    server should remain available.
    
    ```json
    // .app.json
    {
      "apps": {
        "foo": {
          "id": "connector_abc"
        }
      }
    }
    ```
    
    ```json
    // .mcp.json
    {
      "mcpServers": {
        "foo": {
          "url": "https://mcp.foo.com/mcp"
        },
        "foo2": {
          "url": "https://mcp.foo2.com/mcp"
        }
      }
    }
    ```
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Preserve App declaration names in loaded plugin metadata.
    - Keep public effective App outputs as deduped connector IDs for
    existing callers.
    - For ChatGPT/SIWC, suppress only plugin MCP servers whose names match
    declared App names.
    
    ## Validation
    
    ```bash
    cargo fmt --all
    cargo test -p codex-core-plugins plugin_auth_projection
    cargo test -p codex-core-plugins effective_apps
    cargo test -p codex-core-plugins read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning
    cargo test -p codex-core explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins
    cargo test -p codex-core explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth
    cargo test -p codex-app-server --test all plugin_install_filters_disallowed_apps_needing_auth
    git diff --check
    ```
    
    ---------
    
    Co-authored-by: Xin Lin <xl@openai.com>
  • feat(app-server): enforce managed remote control disable (#27961)
    ## Why
    
    Managed deployments need a reliable deny gate for remote control.
    Persisted enablement and explicit startup requests currently remain able
    to start the transport, while the removed `features.remote_control` key
    is intentionally only a compatibility no-op.
    
    This adds a dedicated requirement that administrators can use to force
    remote control off without deleting the user's persisted preference.
    Removing the requirement and restarting restores the prior choice.
    
    ## What Changed
    
    - Added top-level `allow_remote_control` requirements parsing, sourced
    layer precedence, debug output, and `configRequirements/read` exposure
    as `allowRemoteControl`.
    - Added a typed transport policy captured from the startup requirements
    snapshot. Managed disable forces the initial state to disabled and
    prevents enrollment, refresh, connection, and persisted-preference
    mutation.
    - Rejected every `remoteControl/*` RPC before parameter deserialization
    with JSON-RPC `-32600` and `remote control is disabled by managed
    requirements`.
    - Preserved the existing disabled status notification and the previous
    behavior when the requirement is `true` or omitted.
    - Regenerated app-server protocol schemas and documented the new
    requirement.
    
    ## Verification
    
    - Confirmed all remote-control RPCs, including a malformed request,
    return the managed-policy error while the initial status notification
    remains `disabled`.
    - Confirmed explicit ephemeral startup and persisted enablement make no
    backend connection and leave the SQLite preference unchanged.
    - Confirmed `allow_remote_control = true` does not enable or block
    remote control and `configRequirements/read` returns
    `allowRemoteControl: false` for the deny policy.
    
    Related issue: N/A (managed-policy hardening).
  • [codex] add latency tracing spans (#27710)
    ## Why
    
    We have some large gaps in our thread start, resume, and pre-sampling
    traces that make it hard to tell where latency is coming from.
    
    ## What Changed
    
    - Added coarse spans around thread start/resume, turn context
    construction, rollout reconstruction, skill/plugin loading, and tool
    preparation.
    - Added a breakdown of discoverable-tool preparation across connector
    loading, plugin discovery, and local plugin details.
    
    ## Testing
    
    - `cargo check -p codex-app-server -p codex-core -p codex-core-skills -p
    codex-core-plugins`
    - Built the app-server locally and exercised thread start, first turn,
    follow-up turn, server restart, thread resume, and a resumed turn.
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • [codex] add roles to realtime append text (#27936)
    ## Summary
    
    Add an explicit `user` or `developer` role to
    `thread/realtime/appendText` and propagate it through the realtime input
    queue into `conversation.item.create`. Older JSON clients that omit the
    field continue to default to `user`.
    
    This lets app-provided context such as memory retain developer authority
    without bypassing app-server through a renderer-owned data channel. The
    app-server schemas, API documentation, and focused protocol and
    websocket coverage are updated with the new contract.
    
    The Codex Apps consumer is tracked in
    [openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
  • feat: use encrypted local secrets for MCP OAuth (#27541)
    ## Summary
    
    - store MCP OAuth credentials in the configured auth credential backend
    - support encrypted-local OAuth storage, including legacy keyring
    migration
    - propagate the credential backend through MCP refresh, session, CLI,
    and app-server paths
    
    ## Stack
    
    1. #27504 — config and feature flag
    2. #27535 — auth-specific secret namespaces
    3. #27539 — encrypted CLI auth storage
    4. this PR — encrypted MCP OAuth storage
    
    This is a parallel review stack; the original #17931 remains unchanged.
    
    ## Tests
    
    - `just test -p codex-rmcp-client` (the transport round-trip test passed
    after building the required `codex` binary and retrying)
    - `just test -p codex-mcp`
    - `just test -p codex-app-server
    refresh_config_uses_latest_auth_keyring_backend`
    - `just test -p codex-core
    refresh_mcp_servers_is_deferred_until_next_turn`
    - `just test -p codex-cli mcp`
    - `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
    -p codex-app-server -p codex-protocol`
    - `just bazel-lock-check`
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • Handle standalone image generation failures as terminal items (#27920)
    ## Why
    
    Standalone image generation emitted a started item but no terminal item
    when the backend failed. Clients could leave the operation unresolved or
    render it as successful.
    
    ## What changed
    
    - Emit a terminal image-generation item with `status: "failed"` when
    generation or editing fails.
    - Skip image persistence for failed terminal items.
    - Render failed image generation distinctly in TUI history.
    - Preserve the status when handling live and replayed terminal items.
    
    ## Looks for TUI, App-Side change needed 
    
    <img width="867" height="89" alt="image"
    src="https://github.com/user-attachments/assets/9e32342f-a982-411e-8498-456639fc468a"
    />
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - App-server image-generation tests
    - Core stream-event tests
    - TUI image-generation lifecycle and snapshot tests
    - Scoped Clippy and formatting