Commit Graph

767 Commits

  • 2- Use string service tiers in session protocol (#20971)
    ## Summary
    - break service tier session/op/app-server protocol fields from the
    closed enum to string tier ids
    - send the service tier string directly through model requests, prewarm,
    compaction, memories, and TUI/app-server turn starts
    - regenerate app-server protocol JSON/TypeScript schemas, removing the
    standalone ServiceTier TS enum
    
    ## Verification
    - just fmt
    - cargo check -p codex-core -p codex-app-server -p codex-tui
    - just write-app-server-schema
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(app-server): move v2 sessionId onto Thread (#21336)
    ## Why
    
    `session_id` and `thread_id` are separate identities after #20437, but
    app-server only surfaced `sessionId` on the `thread/start`,
    `thread/resume`, and `thread/fork` response envelopes. Other
    thread-bearing surfaces such as `thread/list`, `thread/read`,
    `thread/started`, `thread/rollback`, `thread/metadata/update`, and
    `thread/unarchive` either lacked the grouping key or forced clients to
    special-case those three responses.
    
    Making `sessionId` part of the reusable `Thread` payload gives every v2
    API surface one place to expose session-tree identity.
    
    ## Mental model
      1. thread.sessionId lives on `Thread`
    2. It is a view/runtime identity for the current live session tree, not
    durable stored lineage metadata
    3. When app-server has a live loaded thread, it copies the real value
    from core’s session_configured.session_id
    4. When it only has stored/unloaded data, it falls back to
    thread.sessionId = thread.id
    
    ## What changed
    
    - Added `sessionId` to the v2
    [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109).
    - Removed the duplicate top-level `sessionId` fields from
    `thread/start`, `thread/resume`, and `thread/fork`; clients should now
    read `response.thread.sessionId`.
    - Populated `thread.sessionId` when building live thread responses,
    replaying loaded threads, and returning stored-thread summaries so the
    field is present across start, resume, fork, list, read, rollback,
    metadata-update, unarchive, and `thread/started` paths. See
    [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918)
    and
    [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719).
    - Preserved the stored-thread fallback: if a thread has not been loaded
    into a live session tree yet, `thread.sessionId` falls back to
    `thread.id`; once the thread is live again, the field reports the active
    session tree root.
    - Regenerated the JSON/TypeScript schemas and updated the app-server
    README examples to show
    [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310)
    on the thread object.
  • Move installation ID resolution out of core startup (#21182)
    ## Summary
    
    - resolve or inject the installation ID before core startup and pass it
    through `ThreadManager`, `CodexSpawnArgs`, and `Session` as a plain
    `String`
    - keep child sessions on the parent installation ID instead of
    rediscovering it inside core
    - propagate installation ID startup failures in `mcp-server` instead of
    panicking
    
    ## Why
    
    Core was still touching the filesystem on the session startup path to
    discover `installation_id`. This moves that work to the outer host
    boundary so core no longer depends on `codex_home` reads during session
    construction.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: return session ID from thread/fork (#21332)
    ## Why
    
    `thread/start` and `thread/resume` already return `sessionId`, but
    `thread/fork` only returned the new thread. That left clients to infer
    the forked thread's session identity from `thread.id`, which kept the
    new `session_id` / `thread_id` split implicit at one lifecycle boundary.
    Follow-up to #20437.
    
    ## What changed
    
    - Add `sessionId` to `ThreadForkResponse`.
    - Populate it from the forked session configuration.
    - Regenerate the v2 JSON/TypeScript schema fixtures and update the
    app-server docs/example.
    - Extend the fork integration test to assert the returned `sessionId`.
    
    ## Verification
    
    - Added coverage in `thread_fork_creates_new_thread_and_emits_started`
    for the new response field.
  • feat: add session_id (#20437)
    ## Summary
    
    Related to
    https://openai.slack.com/archives/C095U48JNL9/p1777537279707449
    TLDR:
    We update the meaning of session ids and thread ids:
    * thread_id stays as now
    * session_id become a shared id between every thread under a /root
    thread (i.e. every sub-agent share the same session id)
    
    This PR introduces an explicit `SessionId` and threads it through the
    protocol/client boundary so `session_id` and `thread_id` can diverge
    when they need to, while preserving compatibility for older serialized
    `session_configured` events.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Preserve session MCP config on refresh (#21055)
    # Overview
    MCP refreshes were rebuilding active threads from fresh disk-backed
    config only, which dropped thread-start session overlays such as
    app-injected MCP servers. This keeps refreshes current with disk config
    while preserving the thread-local config that only the active thread
    knows about.
    
    # Changes
    - Rebuild refreshed config per active thread using that thread's current
    `cwd`, rather than fanning out one app-server config to every thread.
    - Preserve each thread's `SessionFlags` layer while replacing reloadable
    config layers with freshly loaded config, then derive the MCP refresh
    payload from the rebuilt result.
    - Move MCP refresh orchestration into app-server so manual refreshes
    fail loudly while background refreshes remain best-effort, and route
    plugin-triggered refreshes through the same per-thread reload path.
    - Add regression coverage for session overlays, fresh project config,
    plugin-derived MCP config, current requirements, and strict vs
    best-effort refresh behavior.
    
    # Verification
    - Passed focused Rust coverage for the thread-config rebuild behavior
    and deferred MCP refresh flow, plus `cargo test -p codex-app-server
    --lib`.
    - Verified end to end in the Codex dev app against the locally built
    CLI: registered an MCP via thread config, verified that it could be used
    successfully before refresh, manually triggered MCP refresh, and
    verified that it continued to be available afterward.
  • app-server: align dynamic tool identifiers with Responses API (#20724)
    ## Why
    
    Codex currently accepts dynamic tool names and namespaces that the
    upstream Responses function-tool path does not actually support. In
    practice, that means app-server can register a dynamic tool successfully
    and only discover later that the LLM-facing tool contract will reject or
    mishandle it.
    
    This PR tightens the app-server-side dynamic tool contract to match the
    Responses API before we stack dynamic tool hook support on top of it.
    
    ## What changed
    
    - validate dynamic tool `name` against the Responses function-tool
    identifier contract: `^[a-zA-Z0-9_-]+$`, length `1..128`
    - validate dynamic tool `namespace` the same way, with the Responses
    namespace length limit `1..64`
    - reject namespaces that collide with the always-reserved Responses
    runtime namespaces such as `functions`, `multi_tool_use`, `file_search`,
    `web`, `browser`, `image_gen`, `computer`, `container`, `terminal`,
    `python`, `python_user_visible`, `api_tool`, `tool_search`, and
    `submodel_delegator`
    - escape invalid identifiers in error messages so control characters do
    not spill raw into logs or client-visible error text
    - document the tightened dynamic tool identifier contract in
    `codex-rs/app-server/README.md`
    - add both unit coverage for the validator and an app-server integration
    test that rejects a `thread/start` request with Responses-incompatible
    dynamic tool identifiers
    
    ## Verification
    
    - `cargo test -p codex-app-server validate_dynamic_tools_`
    - `cargo test -p codex-app-server --test all
    thread_start_rejects_dynamic_tools_not_supported_by_responses`
  • feat: Add plugin share access controls (#21124)
    Extends `plugin/share/save` to accept optional discoverability and
    shareTargets while uploading plugin contents, and adds
    `plugin/share/updateTargets` for share-only target updates without
    re-uploading.
  • [codex-analytics] rework thread_source for thread analytics (#20949)
    ## Summary
    - make `thread_source` an explicit optional thread-level field on
    `thread/start`, `thread/fork`, and returned thread payloads
    - persist `thread_source` in rollout/session metadata so resumed live
    threads retain the original value
    - replace the old best-effort `session_source` -> `thread_source`
    mapping with an explicit caller-supplied analytics classification
    
    ## Why
    Before this change, analytics `thread_source` was populated by a
    best-effort mapping from `session_source`. `session_source` describes
    the runtime/client surface, not the actual thread-level origin, so that
    projection was not accurate enough to distinguish cases such as `user`,
    `subagent`, `memory_consolidation`, and future thread origins reliably.
    
    Making `thread_source` explicit keeps one thread-level analytics field
    while letting callers provide the real classification directly instead
    of recovering it indirectly from `session_source`.
    
    ## Impact
    For new analytics events, `thread_source` now reflects the explicit
    thread-level classification supplied by the caller rather than an
    inferred value derived from `session_source`. Existing protocol fields
    remain optional; callers that omit `threadSource` now produce `null`
    instead of a best-effort inferred value.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo test -p codex-analytics -p codex-core -p
    codex-app-server-protocol --no-run`
    - `cargo test -p codex-app-server-protocol
    generated_ts_optional_nullable_fields_only_in_params`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `cargo test -p codex-core
    resume_stopped_thread_from_rollout_preserves_thread_source`
  • Expose plugin manifest keywords in app server (#21271)
    ## Summary
    - Add plugin manifest keywords to core plugin marketplace/detail models
    - Expose keywords on app-server v2 PluginSummary and generated
    schema/types
    - Populate keywords in plugin/list and plugin/read responses for local
    plugins
    
    Depends on https://github.com/openai/openai/pull/891087
    
    ## Validation
    - just fmt
    - just write-app-server-schema
    - cargo test -p codex-app-server-protocol
    - cargo test -p codex-core-plugins
    - cargo test -p codex-app-server
    plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load
    - cargo test -p codex-app-server
    plugin_read_returns_plugin_details_with_bundle_contents
  • [codex] Move thread naming to app server (#21260)
    ## Why
    
    Thread names are app-server metadata now, backed by the thread store and
    sqlite state database. Keeping a core `SetThreadName` op plus a rollout
    `thread_name_updated` event made rename persistence live in the wrong
    layer and required historical replay support for an event that new
    app-server flows should not write.
    
    ## What changed
    
    - Removed `Op::SetThreadName` and `EventMsg::ThreadNameUpdated` from the
    core protocol and deleted the core handler path that appended rename
    events to rollouts.
    - Updated app-server `thread/name/set` so both loaded and unloaded
    threads write through thread-store metadata and app-server emits
    `thread/name/updated` notifications.
    - Updated local thread-store name metadata updates to write sqlite title
    metadata and the legacy thread-name index without appending rollout
    events.
    - Removed state extraction and rollout handling for the deleted
    thread-name event.
    
    ## Validation
    
    - `cargo test -p codex-app-server thread_name_updated_broadcasts`
    - `cargo test -p codex-app-server
    thread_name_set_is_reflected_in_read_list_and_resume`
    - `cargo test -p codex-thread-store
    update_thread_metadata_sets_name_on_active_rollout_and_indexes_name`
    - `cargo test -p codex-state`
    - `cargo check -p codex-mcp-server -p codex-rollout-trace`
    - `just fix -p codex-app-server -p codex-thread-store -p codex-state -p
    codex-mcp-server -p codex-rollout-trace`
    
    ## Docs
    
    No external documentation update is expected for this internal ownership
    change.
  • Inject state DB, agent graph store (#20689)
    ## Why
    
    We want the agent graph store to be passed down the stack as a real
    dependency, the same way we already treat the thread store.
    
    This will let us inject the agent graph store as a real dependency and
    support implementations other than the local SQLite-backed one. Right
    now most code instantiates a state DB and an agent graph store
    just-in-time. Ideally, we would not depend on the state DB directly but
    only read through the higher-level interfaces.
    
    This change makes the dependency boundaries explicit and moves state DB
    initialization to process bootstrap instead of hiding it inside local
    store implementations.
    
    ## What changed
    
    - `ThreadManager` now requires a `StateDbHandle` and an
    `AgentGraphStore` at construction time instead of treating them as
    optional internals.
    - The local store constructors no longer lazily initialize SQLite.
    Callers now initialize the state DB once per process and use that shared
    handle to build:
      - `LocalThreadStore`
      - `LocalAgentGraphStore`
    - App bootstraps (`app-server`, `mcp-server`, `prompt_debug`, and the
    thread-manager sample) now initialize the state DB up front and inject
    the resulting handle down the stack.
    - `app-server` now consistently uses its process-scoped state DB handle
    instead of reopening SQLite or trying to recover it from loaded threads.
    - Device-key storage now reuses the shared state DB handle instead of
    maintaining its own lazy opener.
    - The thread archive / descendant traversal paths now use the injected
    `AgentGraphStore` instead of reaching through local
    thread-store-specific state.
    
    ## Verification
    
    - `cargo check -p codex-core -p codex-thread-store -p codex-app-server
    -p codex-mcp-server -p codex-thread-manager-sample --tests`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-core
    thread_manager_accepts_separate_agent_graph_store_and_thread_store --
    --nocapture`
    - `cargo test -p codex-app-server
    thread_archive_archives_spawned_descendants -- --nocapture`
  • Auto-deny MCP elicitations for Xcode 26.4 clients (#21113)
    ## Summary
    
    Xcode 26.4 was built against app-server behavior from before MCP
    elicitation requests became client-visible in CLI 0.120.0 via #17043.
    That client line does not expect the new events/messages, so this PR
    restores the old behavior for exactly that client/version combination.
    
    The compatibility handling stays in the app-server layer: when the
    initialized client is `Xcode` and its version starts with `26.4`, the
    app server marks the live Codex thread so MCP elicitations are
    auto-denied. The flag is applied on thread start/resume/fork/turn
    attachment, carried through `Codex`/`CodexThread`, and stored on
    `McpConnectionManager` so refreshed MCP managers preserve the behavior.
    
    ## Notes
    
    This is intentionally narrow and includes a TODO to remove the
    compatibility path once Xcode 26.4 ages out.
  • add turn items view to app-server turns (#21063)
    ## Why
    
    `Turn.items` currently overloads an empty array to mean either that no
    items exist or that the server intentionally did not load them for this
    response. That ambiguity blocks future lazy-loading work where clients
    need to distinguish unloaded, summary, and fully hydrated turn payloads.
    
    ## What changed
    
    - add a new `TurnItemsView` enum with `notLoaded`, `summary`, and `full`
    variants
    - add required `itemsView` metadata to app-server `Turn` payloads
    - mark reconstructed persisted history as `full` and live shell-style
    turn payloads as `notLoaded`
    - keep current `thread/turns/list` behavior unchanged and document that
    it still returns `full` turns today
    - regenerate the JSON and TypeScript protocol fixtures
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server thread_read_can_include_turns`
    - `cargo test -p codex-app-server
    thread_turns_list_can_page_backward_and_forward`
    - `cargo test -p codex-app-server
    thread_resume_rejects_history_when_thread_is_running`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fmt`
  • [codex] Use shared app-server JSON-RPC error helpers (#21221)
    ## Why
    
    App-server had repeated hand-built JSON-RPC error objects for standard
    error shapes. Using the shared helpers keeps the common
    `invalid_request`, `invalid_params`, and `internal_error` construction
    in one place and reduces the chance of new call sites drifting from the
    common error payload shape.
    
    ## What changed
    
    - Replaced manual standard JSON-RPC error object creation with
    `internal_error(...)`, `invalid_request(...)`, and `invalid_params(...)`
    across app-server request processors and runtime paths.
    - Removed local duplicate helper definitions from search and review
    request handling.
    - Preserved existing structured `data` payloads by creating the shared
    helper error first and then attaching the existing metadata.
    - Left custom non-standard errors and raw error-code assertions intact.
    
    ## Validation
    
    - `cargo test -p codex-app-server`
  • hook trust metadata and enforcement (#20321)
    # Why
    
    We want shared hook trust that both the app and the TUI can build on,
    but the metadata is only useful if runtime behavior agrees with it. This
    PR adds a single backend trust model for hooks so unmanaged hooks cannot
    run until the current definition has been reviewed, while managed hooks
    remain runnable and non-configurable.
    
    # What
    
    - persist `trusted_hash` alongside hook state in `config.toml`
    - expose `currentHash` and derived `trustStatus` through `hooks/list`
    - derive trust from normalized hook definitions so equivalent hooks from
    `config.toml` and `hooks.json` share the same trust identity
    - gate unmanaged hooks on trust before they enter the runnable handler
    set
    
    # Reviewer Notes
    
    - key file to review is `codex-rs/hooks/src/engine/discovery.rs`
    - the only **core** change is schema related
  • app-server: ignore persist_extended_history param (#21225)
    ## Why
    
    Taking a step to removing the `persistExtendedHistory` field. It's not
    scalable to be persisting so much data in the rollout file and returning
    it in the thread history.
    
    When a client explicitly sends `true`, the server now tells that client
    the parameter is deprecated and ignored so the caller has a clear
    migration signal via the `deprecationNotice` notification.
    
    ## What changed
    
    - Keep the `persist_extended_history` / `persistExtendedHistory` field
    in the v2 protocol for compatibility, but document it as deprecated and
    ignored.
    - Ignore the parameter in app-server `thread/start`, `thread/resume`,
    and `thread/fork`; those paths always use limited history persistence
    now.
    - Stop treating `persistExtendedHistory` as a running-thread resume
    override mismatch.
    - Emit a connection-scoped `deprecationNotice` when a request explicitly
    sets `persist_extended_history: true`.
    
    ## Verification
    
    - Added `thread_start_deprecates_persist_extended_history_true` to cover
    the deprecation notice.
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-app-server-protocol`
  • Add Windows sandbox readiness RPC (#20708)
    ## Why
    
    The desktop app on Windows needs a read-only way to tell, before the
    next tool call, whether the local Windows sandbox setup is in a state
    that should block the user and ask for setup again.
    
    The main case we want to cover is the elevated sandbox setup version
    bump. Today, if the app is configured for elevated Windows sandboxing
    and the installed setup is stale, the next sandboxed shell/exec path can
    end up triggering the elevated setup flow directly. That means the user
    can see an unexpected UAC prompt with no UI explanation.
    
    This change adds a small app-server preflight so the desktop app can ask
    “is Windows sandbox ready, not configured, or update-required?” during
    startup and show the appropriate blocking UI before the user hits a tool
    call.
    
    ## What changed
    
    - Added a new read-only app-server RPC: `windowsSandbox/readiness`
    - Added a new protocol enum and response type:
      - `WindowsSandboxReadiness`
      - `WindowsSandboxReadinessResponse`
    - Added core readiness logic in `core/src/windows_sandbox.rs`:
      - `ready`
      - `notConfigured`
      - `updateRequired`
    - Wired the new request through `codex_message_processor`
    - Regenerated the vendored app-server schema fixtures
    
    ## Readiness semantics
    
    This is intentionally a coarse startup/version-bump readiness check, not
    a full predictor of every runtime repair case.
    
    For now, readiness is determined from:
    - the configured Windows sandbox level
    - `sandbox_setup_is_complete()` for elevated mode
    
    That means:
    - `disabled` maps to `notConfigured`
    - `restricted token` maps to `ready`
    - `elevated` maps to `ready` or `updateRequired` depending on
    `sandbox_setup_is_complete()`
    
    This is deliberate for the first UI integration because the common case
    we want to catch is “the app updated, the elevated setup version bumped,
    and the user should see an update-required blocker instead of a surprise
    UAC prompt”.
    
    It does not attempt to model every case where the deeper runtime path
    might decide to repair or re-run setup.
    
    ## Testing
    
    - Ran `cargo fmt --all -- app-server-protocol/src/protocol/common.rs
    app-server-protocol/src/protocol/v2.rs
    app-server/src/codex_message_processor.rs core/src/windows_sandbox.rs
    core/src/windows_sandbox_tests.rs`
    - Added unit tests for the pure readiness mapping in
    `core/src/windows_sandbox_tests.rs`
    - Regenerated vendored schema fixtures with `cargo run -p
    codex-app-server-protocol --bin write_schema_fixtures -- --schema-root
    app-server-protocol/schema`
    - Did not run the full cargo test suite
  • Add goal lifecycle metrics (#20799)
    ## Why
    
    Adding goal metrics makes it possible to track how often goals are
    created, completed, and stopped by budget limits, plus the final token
    and wall-clock usage for terminal outcomes.
    
    ## What Changed
    
    - Added OpenTelemetry metric constants for goal lifecycle tracking:
    - `codex.goal.created`: increments each time a new persisted goal is
    created or an existing goal is replaced with a new objective.
    - `codex.goal.completed`: increments when a goal transitions to
    `complete`.
    - `codex.goal.budget_limited`: increments when a goal transitions to
    `budget_limited` because its token budget has been reached.
    - `codex.goal.token_count`: records the final persisted token count when
    a goal transitions to `complete` or `budget_limited`.
    - `codex.goal.duration_s`: records the final persisted elapsed
    wall-clock time, in seconds, when a goal transitions to `complete` or
    `budget_limited`.
    - Emitted creation metrics when a goal is created or replaced.
    - Emitted terminal outcome counters and final usage histograms when a
    goal transitions to `complete` or `budget_limited`, avoiding
    double-counting later in-flight accounting for already budget-limited
    goals.
    - Added focused `codex-core` tests for create/complete metrics and
    one-time budget-limit metrics.
  • 1- Add model service tiers metadata (#20969)
    ## Why
    
    The model list needs to carry display-ready service tier metadata so
    clients can render tier choices with stable IDs, names, and
    descriptions. A raw speed-tier string list is not enough for richer UI
    copy or future tier labels.
    
    ## What changed
    
    - Added `ModelServiceTier` to shared model metadata with string `id`,
    `name`, and `description` fields.
    - Added `service_tiers` to `ModelInfo` and `ModelPreset`, preserving
    empty defaults for older cached model payloads.
    - Exposed `serviceTiers` on app-server v2 `Model` responses and threaded
    it through TUI app-server model conversion.
    - Marked legacy `additional_speed_tiers` / `additionalSpeedTiers`
    metadata as deprecated in source and generated schema output.
    - Regenerated app-server protocol JSON schema and TypeScript fixtures,
    including `ModelServiceTier.ts`.
    
    ## Verification
    
    - Ran `just write-app-server-schema`.
    - Did not run local tests per repo instruction; relying on PR CI.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • codex: migrate (more) app-server thread history reads to ThreadStore (#20575)
    Migrate token usage replay, rollback responses, and detached review
    setup (a special case of forking) to be served from ThreadStore reads
    rather direct rollout files.
    
    - replay restored token usage from already-loaded `RolloutItem` history
    instead of reopening `Thread.path`
    - rebuild rollback responses from loaded `ThreadStore` snapshots and
    history
    - start detached reviews from store-backed parent history and stored
    review-thread metadata
    - remove obsolete app-server rollout-summary helper code that became
    dead after the store-backed migration
    - preserve response/notification ordering for resume, fork, rollback,
    and detached review flows
    - add integration test coverage for the affected paths
  • codex: route metadata updates through ThreadStore (#20576)
    - Route `thread/metadata/update` through
    `ThreadStore::update_thread_metadata`.
    - Add `LocalThreadStore` git metadata patch support for set, partial
    update, and clear semantics.
    - Add some unit tests for the new thread store code
    - Remove a lot of dead code/tests!
  • [codex] Add unsandboxed process exec API (#19040)
    ## Why
    
    App-server clients sometimes need argv-based local process execution
    while sandbox policy is controlled outside Codex. Those environments can
    reject sandbox-disabling paths before a command ever starts, even when
    the caller intentionally wants unsandboxed execution.
    
    This PR adds a distinct `process/*` API for that use case instead of
    extending `command/exec` with another sandbox-disabling shape. Keeping
    the new surface separate also makes the future removal of `command/exec`
    simpler: clients that need explicit process lifecycle control can move
    to the newer handle-based API without depending on `command/exec`
    business logic.
    
    ## What changed
    
    - Added v2 process lifecycle methods: `process/spawn`,
    `process/writeStdin`, `process/resizePty`, and `process/kill`.
    - Added process notifications: `process/outputDelta` for streamed
    stdout/stderr chunks and `process/exited` for final exit status and
    buffered output.
    - Made `process/spawn` intentionally unsandboxed and omitted
    sandbox-selection fields such as `sandboxPolicy` and
    `permissionProfile`.
    - Added client-supplied, connection-scoped `processHandle` values for
    follow-up control requests and notification routing.
    - Supported cwd, environment overrides, PTY mode and size, stdin
    streaming, stdout/stderr streaming, per-stream output caps, and timeout
    controls.
    - Killed active process sessions when the originating app-server
    connection closes.
    - Wired the implementation through the modular `request_processors/`
    app-server layout, with process-handle request serialization for
    follow-up control calls.
    - Updated generated JSON/TypeScript schema fixtures and documented the
    new API in `codex-rs/app-server/README.md`.
    - Added v2 app-server integration coverage in
    `codex-rs/app-server/tests/suite/v2/process_exec.rs` for spawn
    acknowledgement before exit, buffered output caps, and process
    termination.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server`
    
    ---------
    
    Co-authored-by: Owen Lin <owen@openai.com>
  • Remove remote plugin uninstall prefix gate (#20722)
    ## Summary
    
    Remove the hardcoded remote plugin ID prefix allow-list from app-server
    uninstall routing. IDs that do not parse as local `plugin@marketplace`
    IDs now flow through the remote uninstall path, where the existing
    remote ID safety validation still rejects empty IDs, spaces, slashes,
    and other unsafe characters before URL/cache use.
    
    ## Why
    
    Plugin-service owns the backend remote plugin ID contract. Codex should
    not require remote IDs to start with the local hardcoded prefixes
    `plugins~`, `plugins_`, `app_`, `asdk_app_`, or `connector_`, because
    newer backend ID families could otherwise be rejected before
    plugin-service sees the request.
    
    ## Validation
    
    - `just fmt`
    - `cargo test -p codex-app-server plugin_uninstall`
    - `just fix -p codex-app-server`
    - `git diff --check`
  • [codex-analytics] add item lifecycle timing (#20514)
    ## Why
    
    Tool families already disagree on what their existing `duration` fields
    mean, so lifecycle latency should live on the shared item envelope
    instead of being inferred from per-tool execution fields. Carrying that
    envelope through app-server notifications gives downstream consumers one
    reusable timing signal without pretending every tool has the same
    execution semantics.
    
    ## What changed
    
    - Adds `started_at_ms` to core `ItemStartedEvent` values and
    `completed_at_ms` to core `ItemCompletedEvent` values.
    - Populates those timestamps in the shared session lifecycle emitters,
    so protocol-native items get timing without each producer tracking its
    own clock state.
    - Exposes `startedAtMs` on app-server `item/started` notifications and
    `completedAtMs` on `item/completed` notifications.
    - Maps the lifecycle timestamps through the app-server boundary while
    leaving legacy-converted notifications nullable when no lifecycle
    timestamp exists.
    - Regenerates the app-server JSON schema and TypeScript fixtures for the
    notification-envelope change and updates downstream fixtures that
    construct those notifications directly.
    - Extends the existing web-search and image-generation integration flows
    to assert the new lifecycle timestamps on the native item events.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-tui -p codex-exec
    -p codex-app-server-client`
    - `cargo test -p codex-core --test all web_search_item_is_emitted`
    - `cargo test -p codex-core --test all
    image_generation_call_event_is_emitted`
    - `cargo test -p codex-app-server-protocol`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20514).
    * #18748
    * #18747
    * #17090
    * #17089
    * __->__ #20514
  • state: pass state db handles through consumers (#20561)
    ## Why
    
    SQLite state was still being opened from consumer paths, including lazy
    `OnceCell`-backed thread-store call sites. That let one process
    construct multiple state DB connections for the same Codex home, which
    makes SQLite lock contention and `database is locked` failures much
    easier to hit.
    
    State DB lifetime should be chosen by main-like entrypoints and tests,
    then passed through explicitly. Consumers should use the supplied
    `Option<StateDbHandle>` or `StateDbHandle` and keep their existing
    filesystem fallback or error behavior when no handle is available.
    
    The startup path also needs to keep the rollout crate in charge of
    SQLite state initialization. Opening `codex_state::StateRuntime`
    directly bypasses rollout metadata backfill, so entrypoints should
    initialize through `codex_rollout::state_db` and receive a handle only
    after required rollout backfills have completed.
    
    ## What Changed
    
    - Initialize the state DB in main-like entrypoints for CLI, TUI,
    app-server, exec, MCP server, and the thread-manager sample.
    - Pass `Option<StateDbHandle>` through `ThreadManager`,
    `LocalThreadStore`, app-server processors, TUI app wiring, rollout
    listing/recording, personality migration, shell snapshot cleanup,
    session-name lookup, and memory/device-key consumers.
    - Remove the lazy local state DB wrapper from the thread store so
    non-test consumers use only the supplied handle or their existing
    fallback path.
    - Make `codex_rollout::state_db::init` the local state startup path: it
    opens/migrates SQLite, runs rollout metadata backfill when needed, waits
    for concurrent backfill workers up to a bounded timeout, verifies
    completion, and then returns the initialized handle.
    - Keep optional/non-owning SQLite helpers, such as remote TUI local
    reads, as open-only paths that do not run startup backfill.
    - Switch app-server startup from direct
    `codex_state::StateRuntime::init` to the rollout state initializer so
    app-server cannot skip rollout backfill.
    - Collapse split rollout lookup/list APIs so callers use the normal
    methods with an optional state handle instead of `_with_state_db`
    variants.
    - Restore `getConversationSummary(ThreadId)` to delegate through
    `ThreadStore::read_thread` instead of a LocalThreadStore-specific
    rollout path special case.
    - Keep DB-backed rollout path lookup keyed on the DB row and file
    existence, without imposing the filesystem filename convention on
    existing DB rows.
    - Verify readable DB-backed rollout paths against `session_meta.id`
    before returning them, so a stale SQLite row that points at another
    thread's JSONL falls back to filesystem search and read-repairs the DB
    row.
    - Keep `debug prompt-input` filesystem-only so a one-off debug command
    does not initialize or backfill SQLite state just to print prompt input.
    - Keep goal-session test Codex homes alive only in the goal-specific
    helper, rather than leaking tempdirs from the shared session test
    helper.
    - Update tests and call sites to pass explicit state handles where DB
    behavior is expected and explicit `None` where filesystem-only behavior
    is intended.
    
    ## Validation
    
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p
    codex-rollout -p codex-thread-store -p codex-app-server -p codex-core -p
    codex-tui -p codex-exec -p codex-cli --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout state_db_`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout try_init_ -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo clippy -p
    codex-rollout --lib -- -D warnings`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store
    read_thread_falls_back_when_sqlite_path_points_to_another_thread --
    --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    shell_snapshot`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all personality_migration`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find::find_prefers_sqlite_path_by_id --
    --nocapture`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    interrupt_accounts_active_goal_before_pausing`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server get_auth_status -- --test-threads=1`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server --lib`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p codex-rollout
    -p codex-app-server --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout
    -p codex-thread-store -p codex-core -p codex-app-server -p codex-tui -p
    codex-exec -p codex-cli`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout -p
    codex-app-server`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p
    codex-rollout`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-core`
    - `just argument-comment-lint -p codex-core`
    - `just argument-comment-lint -p codex-rollout`
    
    Focused coverage added in `codex-rollout`:
    
    - `recorder::tests::state_db_init_backfills_before_returning` verifies
    the rollout metadata row exists before startup init returns.
    - `state_db::tests::try_init_waits_for_concurrent_startup_backfill`
    verifies startup waits for another worker to finish backfill instead of
    disabling the handle for the process.
    -
    `state_db::tests::try_init_times_out_waiting_for_stuck_startup_backfill`
    verifies startup does not hang indefinitely on a stuck backfill lease.
    -
    `tests::find_thread_path_accepts_existing_state_db_path_without_canonical_filename`
    verifies DB-backed lookup accepts valid existing rollout paths even when
    the filename does not include the thread UUID.
    -
    `tests::find_thread_path_falls_back_when_db_path_points_to_another_thread`
    verifies DB-backed lookup ignores a stale row whose existing path
    belongs to another thread and read-repairs the row after filesystem
    fallback.
    
    Focused coverage updated in `codex-core`:
    
    - `rollout_list_find::find_prefers_sqlite_path_by_id` now uses a
    DB-preferred rollout file with matching `session_meta.id`, so it still
    verifies that valid SQLite paths win without depending on stale/empty
    rollout contents.
    
    `cargo test -p codex-app-server thread_list_respects_search_term_filter
    -- --test-threads=1 --nocapture` was attempted locally but timed out
    waiting for the app-server test harness `initialize` response before
    reaching the changed thread-list code path.
    
    `bazel test //codex-rs/thread-store:thread-store-unit-tests
    --test_output=errors` was attempted locally after the thread-store fix,
    but this container failed before target analysis while fetching `v8+`
    through BuildBuddy/direct GitHub. The equivalent local crate coverage,
    including `cargo test -p codex-thread-store`, passes.
    
    A plain local `cargo check -p codex-rollout -p codex-app-server --tests`
    also requires system `libcap.pc` for `codex-linux-sandbox`; the
    follow-up app-server check above used `CODEX_SKIP_VENDORED_BWRAP=1` in
    this container.
  • feat(app-server): always return limited thread history (#20682)
    ## Why
    
    Whenever we return a thread's history (turns and items) over app-server,
    always return the limited form as specified by the rollout policy
    `EventPersistenceMode::Limited`, even if the thread was previously
    started with `EventPersistenceMode::Extended`.
    
    We're finding it is quite unscalable to be returning the extended
    history, so let's apply the same filtering logic of the rollout policy
    when we load and return the thread's history.
    
    ## What Changed
    
    - Reuse the rollout persistence policy when reconstructing app-server
    `ThreadItem` history so only `EventPersistenceMode::Limited` rollout
    items are replayed into API turns.
    - Route `thread/read`, `thread/resume`, `thread/fork`,
    `thread/turns/list`, and rollback responses through the same filtered
    app-server history projection.
    - Keep live active turns intact when composing a response for a
    currently running thread.
    - Update command execution coverage so persisted extended command events
    are excluded from returned history for `thread/read`, `thread/fork`, and
    `thread/turns/list`.
    
    ## Test Plan
    
    - `cargo test -p codex-app-server limited`
    - `cargo test -p codex-app-server thread_shell_command`
    - `cargo test -p codex-app-server thread_read`
    - `cargo test -p codex-app-server thread_rollback`
    - `cargo test -p codex-app-server thread_fork`
    - `cargo test -p codex-app-server-protocol`
  • [codex] Split app-server request processors (#20940)
    ## Why
    
    The app-server request path had grown around a large
    `CodexMessageProcessor` plus separate API wrapper/helper modules. That
    made the dependency graph hard to see and forced unrelated request
    families to share broad processor state.
    
    This PR makes the split mechanical and command-prefix oriented so
    request families own only the dependencies they use.
    
    ## What changed
    
    - Replaced `CodexMessageProcessor` with command-prefix request
    processors under `app-server/src/request_processors/`.
    - Removed the old config, device-key, external-agent-config, and fs API
    wrapper files by moving their API handling into processors.
    - Split apps, plugins, marketplace, catalog, account, MCP, command exec,
    fs, git, feedback, thread, turn, thread goals, and Windows sandbox
    handling into dedicated processors.
    - Kept shared lifecycle, summary conversion, token usage replay, and
    shared error mapping only where multiple processors use them; single-use
    helpers were inlined into their owning processor.
    - Removed the fallback processor path and moved processor tests to
    `_tests` files.
    
    ## Validation
    
    - `cargo test -p codex-app-server`
    - `cargo check -p codex-app-server`
    - `just fix -p codex-app-server`
  • [codex] Emit MCP tool calls as turn items (#20677)
    ## Why
    
    `McpToolCall` was still an app-server item synthesized from deprecated
    legacy begin/end events. Recent item migrations moved this ownership
    into core `TurnItem`s, so MCP tool calls now follow the same canonical
    lifecycle and leave legacy events as compatibility fanout.
    
    Keeping the core item close to the v2 `ThreadItem::McpToolCall` shape
    also avoids spreading MCP result semantics across app-server conversion
    code. Core now owns whether a completed call is `completed` or `failed`,
    and whether the payload is a tool result or an error.
    
    ## What changed
    
    - Added core `TurnItem::McpToolCall` with flattened `server`, `tool`,
    `arguments`, `status`, `result`, and `error` fields.
    - Updated MCP tool call emitters, including MCP resource tools, to emit
    `ItemStarted`/`ItemCompleted` around directly constructed core MCP
    items.
    - Updated app-server v2 conversion to project the core MCP item into
    `ThreadItem::McpToolCall` without deriving status or splitting `Result`
    locally.
    - Ignored live deprecated MCP legacy fanout in app-server v2 to avoid
    duplicate item notifications, while keeping thread history replay on the
    legacy event path.
    
    ## Verification
    
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core --lib mcp_tool_call`
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server
    mcp_tool_call_completion_notification_contains_truncated_large_result`
  • [codex] Refactor app-server dispatch result flow (#20897)
    ## Why
    
    App-server request handling had response sending spread across many
    individual handlers, which made it harder to see which requests return
    payloads, which methods send their own delayed response, and which
    branches emit notifications after a response.
    
    ## What changed
    
    - Centralized normal `ClientResponsePayload` sending in the dispatch
    path.
    - Kept explicit-response methods explicit where they need custom
    ordering or delayed delivery.
    - Removed forward-only handler wrappers and immediate `async { ...
    }.await` bodies where they were not needed.
    - Moved branch-specific post-response notifications into the branches
    that own the response ordering.
    - Replaced unreachable delegated request-family error arms with explicit
    `unreachable!` cases.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server thread_goal`
    - `just fix -p codex-app-server`
  • [codex] Emit image view as core item (#20512)
    ## Why
    
    Image-view results should be represented as a core-produced turn item
    instead of being reconstructed by app-server. At the same time, existing
    rollout/history paths still understand the legacy `ViewImageToolCall`
    event, so this keeps that event as compatibility output generated from
    the new item lifecycle.
    
    ## What changed
    
    - Added `TurnItem::ImageView` to `codex-protocol`.
    - Emitted image-view item start/completion directly from the core
    `view_image` handler.
    - Kept `ViewImageToolCall` as a legacy event and generate it from
    completed `TurnItem::ImageView` items.
    - Kept `thread_history.rs` on the legacy `ViewImageToolCall` replay
    path, with `ImageView` item lifecycle events ignored there.
    - Updated app-server protocol conversion, rollout persistence, and
    affected exhaustive event matches for the new item plus legacy fan-out
    shape.
    
    ## Verification
    
    - `cargo test -p codex-protocol -p codex-app-server-protocol -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server -p
    codex-app-server --lib`
    - `cargo test -p codex-core --test all
    view_image_tool_attaches_local_image`
    - `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-rollout -p codex-rollout-trace -p
    codex-mcp-server`
    - `git diff --check`
  • Use selected turn environments for runtime context (#20281)
    ## Summary
    - make selected turn environments the source of truth for session
    runtime cwd and MCP runtime environment selection
    - keep local/no-selection fallback behavior intact
    - add coverage for duplicate selected environments, cwd resolution, and
    MCP runtime environment selection
    
    ## Validation
    - git diff --check
    - rustfmt was run on touched Rust files during the implementation
    workflow
    
    CI should provide the full Bazel/test signal.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Migrate loaded thread/read history to ThreadStore (#20486)
    ## Summary
    
    - Route loaded `thread/read` + `includeTurns` through
    `CodexThread::load_history` / ThreadStore history instead of direct
    rollout JSONL reads.
    - Add an in-memory ThreadStore regression test covering loaded
    `thread/read includeTurns` without a local rollout path.
  • app-server: move transport into dedicated crate (#20545)
    ## Why
    
    `codex-app-server` currently owns both request-processing code and
    transport implementation details. Splitting the transport layer into its
    own crate makes that boundary explicit, reduces the amount of
    transport-specific dependency surface carried by `codex-app-server`, and
    gives future transport work a narrower place to evolve.
    
    ## What changed
    
    - Added `codex-app-server-transport` and moved the existing transport
    tree into it, including stdio, unix socket, websocket, remote-control
    transport, and websocket auth.
    - Moved shared transport-facing message types into the new crate so both
    the transport implementation and `codex-app-server` use the same
    definitions.
    - Kept processor-facing connection state and outbound routing in
    `codex-app-server`, with the routing tests moved next to that local
    wrapper.
    - Updated workspace metadata, Bazel crate metadata, and
    `codex-app-server` dependencies for the new crate boundary.
    
    ## Validation
    
    - `cargo metadata --locked --no-deps`
    - `git diff --check`
    - Attempted `cargo test -p codex-app-server-transport`, `cargo test -p
    codex-app-server`, `just fix -p codex-app-server-transport`, and `just
    fix -p codex-app-server`; all were blocked before compilation by the
    existing `packageproxy` resolution failure for locked `rustls-webpki =
    0.103.13`.
    - Attempted Bazel build / lockfile validation; those were blocked by
    external fetch failures against BuildBuddy / GitHub while resolving
    `v8`.
  • Move apply-patch file changes into turn items (#20540)
    ## Why
    
    Apply-patch file changes are now part of the core turn item stream, so
    v2 clients can consume the same first-class item lifecycle path used by
    other turn items instead of relying on app-server-specific remapping
    from legacy patch events.
    
    ## What changed
    
    - Added a core `TurnItem::FileChange` carrying apply-patch changes and
    completion metadata.
    - Updated the apply-patch tool emitter to send `ItemStarted` /
    `ItemCompleted` with the new `FileChange` item while preserving legacy
    `PatchApplyBegin` / `PatchApplyEnd` fan-out.
    - Updated app-server v2 conversion to render the new core item directly
    and stopped `event_mapping` from remapping old patch begin/end events
    into item notifications.
    - Kept thread history reconstruction based on the existing old
    apply-patch events for rollout compatibility.
    
    ## Verification
    
    - `cargo test -p codex-protocol -p codex-app-server-protocol`
    - `cargo test -p codex-core --test all
    apply_patch_tool_executes_and_emits_patch_events`
    - `cargo test -p codex-app-server bespoke_event_handling`
  • feat: Track local paths for shared plugins (#20560)
    When a local plugin is shared, Codex now records the local plugin path
    by remote plugin id under CODEX_HOME/.tmp.
    
    plugin/share/list includes the remote share URL and the matching local
    plugin path when available, and plugin/share/delete
    clears the local mapping after deleting the remote share.
    
    Also add sharedURL to plugin/share/list.
  • Add remote plugin skill read API (#20150)
    ## Summary
    
    Adds an app-server `plugin/skill/read` method for remote plugin skill
    markdown. The new method calls the plugin-service skill detail endpoint
    and returns `skill_md_contents`, so clients can preview skills for
    remote plugins before the bundle is installed locally.
    
    ## Why
    
    Uninstalled remote plugin skills do not have local `SKILL.md` files.
    Without an on-demand remote read, the desktop plugin details UI cannot
    render the skill details modal for those skills.
    
    ## Validation
    
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server --test all --
    suite::v2::plugin_read::plugin_skill_read_reads_remote_skill_contents_when_remote_plugin_enabled
    --exact`
    - `just fix -p codex-app-server-protocol -p codex-core-plugins -p
    codex-app-server`
  • Refresh remote plugin cache on auth changes (#20265)
    ## Summary
    - Refresh the remote installed-plugin cache after login/logout instead
    of keying it by account or eagerly clearing it.
    - Reuse the existing single-flight remote installed refresh loop so
    newer queued auth refreshes replace older pending requests and the API
    result eventually overwrites or clears the cache.
    - Keep derived plugin/skills cache and MCP refresh side effects behind
    the existing effective-plugin-changed task when the refreshed installed
    state changes.
    - Leave `clear_plugin_related_caches` scoped to derived plugin/skills
    caches so share mutations do not drop remote installed plugins.
    
    ## Tests
    - `cargo fmt --all --manifest-path codex-rs/Cargo.toml` (passes; stable
    rustfmt warns that `imports_granularity = Item` is nightly-only)
    - `cargo test -p codex-core-plugins remote_installed_cache`
    - `cargo test -p codex-app-server
    skills_list_loads_remote_installed_plugin_skills_from_cache`
  • Make thread store process-scoped (#19474)
    - Build one app-server process ThreadStore from startup config and share
    it with ThreadManager and CodexMessageProcessor.
    - Remove per-thread/fork store reconstruction so effective thread config
    cannot switch the persistence backend.
    - Add params to ThreadStore create/resume for specifying thread
    metadata, since otherwise the metadata from store creation would be used
    (incorrectly).
  • Surface admin-disabled remote plugin status (#20298)
    ## Summary
    
    Remote plugin-service returns plugin availability separately from a
    user's installed/enabled state. This adds `PluginAvailabilityStatus` to
    the app-server protocol, propagates remote catalog `status` into
    `PluginSummary`, and rejects install attempts for remote plugins marked
    `DISABLED_BY_ADMIN` before downloading or caching the bundle.
    
    This is the `openai/codex` half of the change. The companion
    `openai/openai` webview PR is
    https://github.com/openai/openai/pull/873269.
    
    ## Validation
    
    - `cargo run -p codex-app-server-protocol --bin write_schema_fixtures`
    - `cargo test -p codex-app-server --test all
    plugin_list_marks_remote_plugin_disabled_by_admin`
    - `cargo test -p codex-app-server --test all
    plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
    - `cargo test -p codex-app-server --test all
    plugin_install_rejects_remote_plugin_disabled_by_admin_before_download`
    - `cargo test -p codex-app-server-protocol schema_fixtures`
  • app-server: switch remote control to protocol v3 segmentation (#20341)
    ## Why
    
    Remote-control protocol v3 makes segmentation an explicit wire-level
    feature. The app-server transport needs to support that protocol
    directly so large messages can be chunked, acknowledged, replayed, and
    reassembled consistently.
    
    ## What changed
    
    - Bump the remote-control websocket protocol version from `2` to `3`.
    - Add explicit client/server chunk envelope variants plus chunk-aware
    acknowledgements.
    - Split oversized outbound server messages into bounded transport
    chunks.
    - Reassemble ordered inbound client chunks with bounded memory usage and
    stream/client invalidation handling.
    - Track inbound chunk cursors and outbound ack cursors as `(seq_id,
    segment_id)` so duplicate chunks and partial replays behave correctly.
    - Add focused coverage for chunk splitting, reassembly, duplicate
    suppression, and stream replacement behavior.
    
    ## Validation
    
    - Added targeted unit coverage for segmented message handling in
    `remote_control`.
    - Local validation is currently blocked before compilation because
    `packageproxy` does not serve the locked `rustls-webpki 0.103.13`
    dependency required by the workspace.
  • Emit analytics for remote plugin installs (#20267)
    ## Summary
    
    - emit `codex_plugin_installed` after a remote plugin install succeeds
    - keep local installs unchanged, but let remote installs override the
    analytics `plugin_id` with the backend remote plugin id
    (`plugins~Plugin_...`)
    - preserve the local/display identity in `plugin_name` and
    `marketplace_name`, plus capability metadata from the installed bundle
    - add regression coverage for local install analytics, remote install
    analytics, and analytics id override serialization
    
    ## Testing
    
    - `just fmt`
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-app-server`
  • fix(tui): set persist_extended_history: false (#20502)
    Large rollouts are no good. This updates the TUI to behave the same as
    the Codex App, which is also turning it off.
  • Sync remote installed plugin bundles (#20268)
    ## Summary
    - Download missing remote installed plugin bundles during app-server
    startup and plugin/list refresh.
    - Upgrade cached remote installed bundles when the backend installed
    version changes.
    - Remove stale remote installed bundle caches without writing remote
    plugin state into config.toml.
    
    ## Review note
    This is a clean PR branch cut from the current diff on top of latest
    `origin/main`. The diff intentionally has no `codex-rs/core/**` files,
    so CODEOWNERS should not request the core-directory owner review from
    stale PR history.
    
    ## Validation
    Already run on the source branch before creating this clean PR:
    - `just fmt`
    - `cargo test -p codex-core-plugins`
    - `cargo test -p codex-app-server --test all
    app_server_startup_sync_downloads_remote_installed_plugin_bundles --
    --nocapture`
    - `cargo test -p codex-app-server --test all
    plugin_list_sync_upgrades_and_removes_remote_installed_plugin_bundles --
    --nocapture`
    - `cargo test -p codex-app-server --test all
    app_server_startup_remote_plugin_sync_runs_once -- --nocapture`
    - `just fix -p codex-core-plugins`
    - `just fix -p codex-app-server`
    - `git diff --check`
  • [codex] Migrate thread turns list to thread store (#19280)
    - migrate `thread/turns/list` to ThreadStore. Uses ThreadStore for most
    data now but merges in the in-memory state from thread manager
    - keep v2 `thread/list` pathless-store friendly by converting
    `StoredThread` directly to API `Thread`
    - add regression coverage for pathless store history/listing
  • Stop emitting item/fileChange/outputDelta output delta notifications (#20471)
    ## Why
    
    `item/fileChange/outputDelta` text output was only the tool's summary or
    error text and not used by client surfaces.
    
    We keep `item/fileChange/outputDelta` in the app-server protocol as a
    deprecated compatibility entry, but the server no longer emits it.
    
    ## What changed
    
    - stop the `apply_patch` runtime from emitting `ExecCommandOutputDelta`
    events
    - simplify `item_event_to_server_notification` so command output deltas
    always map to `item/commandExecution/outputDelta`
    - remove the app-server bookkeeping that tried to detect whether an
    output delta belonged to a file change
    - mark `item/fileChange/outputDelta` as a deprecated legacy protocol
    entry in the v2 types, schema, and README
    - simplify the file-change approval tests so they only wait for
    completion instead of expecting output-delta notifications
    
    ## Testing
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-thread-manager-sample`
    - `cargo test -p codex-app-server-protocol
    protocol::event_mapping::tests::exec_command_output_delta_maps_to_command_execution_output_delta
    -- --exact`
    - `cargo test -p codex-app-server
    turn_start_file_change_approval_accept_for_session_persists_v2 --
    --exact` *(failed before the test assertions because the wiremock
    `/responses` mock received 0 requests in setup)*
  • Move item event mapping into app-server-protocol (#20299)
    ## Why
    
    Follow-up to #20291.
    
    The v2 item-event-to-notification translation had been embedded in
    `app-server/src/bespoke_event_handling.rs`, which made it hard to reuse
    anywhere else. This PR moves that stateless mapping into shared protocol
    code so other entry points can produce the same `ServerNotification`
    payloads without copying app-server logic.
    
    That also lets `thread-manager-sample` demonstrate the same notification
    surface that the app server exposes, instead of only printing the final
    assistant message.
    
    ## What changed
    
    - move `item_event_to_server_notification` into
    `codex-app-server-protocol::protocol::event_mapping`
    - keep the mapper tests next to the shared implementation in
    `codex-app-server-protocol`
    - re-export the mapper from `codex-core-api` so lightweight consumers
    can use it without reaching into `app-server-protocol` directly
    - simplify `app-server/src/bespoke_event_handling.rs` so it delegates
    the stateless event-to-notification projection to the shared helper
    - update `thread-manager-sample` to:
      - print mapped notifications as newline-delimited JSON
      - use the shared mapper through `codex-core-api`
    - enable the default feature set so the sample exposes the normal tool
    surface
    - use a `read_only` permission profile so shell commands can run in the
    sample without widening permissions
    
    ## Testing
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core-api`
    - `cargo test -p codex-app-server bespoke_event_handling::tests`
    - `cargo test -p codex-thread-manager-sample`
    - `cargo run -p codex-thread-manager-sample -- "briefly explore the repo
    with pwd and ls, then summarize it"`
  • realtime: rename provider session ids (#20361)
    ## Summary
    
    Codex is repurposing `session` to mean a thread group, so the realtime
    provider session id should no longer use `session_id` / `sessionId` in
    Codex-facing protocol payloads. This PR renames that provider-specific
    field to `realtime_session_id` / `realtimeSessionId` and intentionally
    breaks clients that still send the old field names.
    
    ## What Changed
    
    - Renamed realtime provider session fields in `ConversationStartParams`,
    `RealtimeConversationStartedEvent`, and `RealtimeEvent::SessionUpdated`.
    - Renamed app-server v2 realtime request and notification fields to
    `realtimeSessionId`.
    - Removed legacy serde aliases for `session_id` / `sessionId`; clients
    must send the new names.
    - Propagated the rename through core realtime startup, app-server
    adapters, codex-api websocket handling, and TUI realtime state.
    - Regenerated app-server protocol schema/TypeScript outputs and updated
    app-server README examples.
    - Kept upstream Realtime API concepts unchanged: provider `session.id`
    parsing and `x-session-id` headers still use the upstream wire names.
    
    ## Testing
    
    - CI is running on the latest pushed commit.
    - Earlier local verification on this PR:
      - `cargo test -p codex-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-core
    realtime_conversation`
      - `cargo test -p codex-app-server-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-app-server
    realtime_conversation`
    - attempted `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-tui` (local
    linker bus error while linking the test binary)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>