Commit Graph

507 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.
  • 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>
  • 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
  • chore(app-server-protocol): split v2 API definitions into modules (#21251)
    ## Why
    
    `codex-rs/app-server-protocol/src/protocol/v2.rs` had grown into a
    single ~12k-line definition file for the entire app-server v2 API.
    
    This is purely a mechanical refactor to break up the monolithic `v2.rs`
    file that contains all app-server API v2 types into more modular files,
    grouped by resource (e.g. account, thread, turn, etc.).
    
    `just write-app-server-schema` shows no real changes, so we can be sure
    that this is purely an internal organizational change.
    
    ## What changed
    
    - Replaced the monolithic `protocol/v2.rs` with a `protocol/v2/` module
    tree and a small `mod.rs` that only declares and reexports modules.
    - Grouped v2 API definitions by conceptual owner, including `account`,
    `apps`, `collaboration_mode`, `command_exec`, `config`, `device_key`,
    `experimental_feature`, `feedback`, `fs`, `hook`, `item`, `mcp`,
    `model`, `notification`, `permissions`, `plugin`, `process`, `realtime`,
    `review`, `thread`, `thread_data`, `turn`, and `windows_sandbox`.
    - Moved v2 tests into `protocol/v2/tests.rs` so `mod.rs` stays small.
    - Kept shared protocol helpers in `protocol/v2/shared.rs`, including the
    enum mirroring macro and common cross-resource types.
    - Co-located resource-specific notifications and server-request payloads
    with the modules that own those resources.
    - Regenerated app-server protocol schema fixtures. The schema diffs are
    non-semantic newline-only changes after the refactor.
    
    ## Verification
    
    - `cargo check -p codex-app-server-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `just write-app-server-schema`
  • 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`
  • 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
  • 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] 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>
  • [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
  • 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`
  • feat: add remote compaction v2 Responses client path (#20773)
    ## Why
    
    This adds the `remote_compaction_v2` client path so remote compaction
    can run through the normal Responses stream and install a
    `context_compaction` item that trigger a compaction.
    
    The goal is to migrate some of the compaction logic on the client side
    
    We keeps the v2 transport behind a feature flag while letting follow-up
    requests reuse the compacted context instead of falling back to the
    legacy compaction item shape.
    
    ## What changed
    
    - add `ResponseItem::ContextCompaction` and refresh the generated
    app-server / schema / TypeScript fixtures that expose response items on
    the wire
    - add `core/src/compact_remote_v2.rs` to send compaction through the
    standard streamed Responses client, require exactly one
    `context_compaction` output item, and install that item into compacted
    history
    - route manual compact and auto-compaction through the v2 path when
    `remote_compaction_v2` is enabled, while keeping the existing remote
    compaction path as the fallback
    - preserve the new item type across history retention, follow-up request
    construction, telemetry, rollout persistence, and rollout-trace
    normalization
    - add targeted coverage for the feature flag, `context_compaction`
    serialization, rollout-trace normalization, and remote-compaction
    follow-up behavior
    
    ## Verification
    
    - added protocol tests for `context_compaction`
    serialization/deserialization in `protocol/src/models.rs`
    - added rollout-trace coverage for `context_compaction` normalization in
    `rollout-trace/src/reducer/conversation_tests.rs`
    - added remote compaction integration coverage for v2 follow-up reuse
    and mixed compaction output streams in
    `core/tests/suite/compact_remote.rs`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [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] 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`
  • 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`
  • [codex] Remove unused event messages (#20511)
    ## Why
    
    Several legacy `EventMsg` variants were still emitted or mapped even
    though clients either ignored them or had moved to item/lifecycle
    events. `Op::Undo` had also degraded to an unavailable shim, so this
    removes that dead task path instead of preserving a command that cannot
    do useful work.
    
    `McpStartupComplete`, `WebSearchBegin`, and `ImageGenerationBegin` are
    intentionally kept because useful consumers still depend on them: MCP
    startup completion drives readiness behavior, and the begin events let
    app-server/core consumers surface in-progress web-search and
    image-generation items before the final payload arrives.
    
    ## What Changed
    
    - Removed weak legacy event variants and payloads from `codex-protocol`,
    including legacy agent deltas, background events, and undo lifecycle
    events.
    - Kept/restored `EventMsg::McpStartupComplete`,
    `EventMsg::WebSearchBegin`, and `EventMsg::ImageGenerationBegin` with
    serializer and emission coverage.
    - Updated core, rollout, MCP server, app-server thread history,
    review/delegate filtering, and tests to rely on the useful replacement
    events that remain.
    - Removed `Op::Undo`, `UndoTask`, the undo test module, and stale TUI
    slash-command comments.
    - Stopped agent job/background progress and compaction retry notices
    from emitting `BackgroundEvent` payloads.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-app-server-protocol -p
    codex-core -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-protocol -p codex-app-server-protocol -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-core --test all suite::items`
    - `just fix -p codex-protocol -p codex-app-server-protocol -p codex-core
    -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - Earlier coverage on this PR also included `codex-mcp`, `codex-tui`,
    core library tests, MCP/plugin/delegate/review/agent job tests, and MCP
    startup TUI tests.
  • 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`
  • fix(app-server): mark thread/turns/list and exclude_turns as experime… (#20499)
    …ntal
    
    We have some bugs to work out and it is not quite ready to consume as a
    public API.
  • 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>
  • feat: Add workspace plugin sharing APIs (#20278)
    1. Adds v2 plugin/share/save, plugin/share/list, and plugin/share/delete
    RPCs.
    2. Implements save by archiving a local plugin root, enforcing a size
    limit, uploading through the workspace upload flow, and supporting
    updates via remotePluginId.
    3. Lists created workspace plugins
    4. Deletes a previously uploaded/shared plugin.
  • Add persisted hook enablement state (#19840)
    ## Why
    
    After `hooks/list` exposes the hook inventory, clients need a way to
    persist user hook preferences, make those changes effective in
    already-open sessions, and distinguish user-controllable hooks from
    managed requirements without adding another bespoke app-server write
    API.
    
    ## What
    
    - Extends `hooks/list` entries with effective `enabled` state.
    - Persists user-level hook state under `hooks.state.<hook-id>` so the
    model can grow beyond a single boolean over time.
    - Uses the existing `config/batchWrite` path for hook state updates
    instead of introducing a dedicated hook write RPC.
    - Refreshes live session hook engines after config writes so
    already-open threads observe updated enablement without a restart.
    
    ## Stack
    
    1. openai/codex#19705
    2. openai/codex#19778
    3. This PR - openai/codex#19840
    4. openai/codex#19882
    
    ## Reviewer Notes
    
    The generated schema files account for much of the raw diff. The core
    behavior is in:
    
    - `hooks/src/config_rules.rs`, which resolves per-hook user state from
    the config layer stack.
    - `hooks/src/engine/discovery.rs`, which projects effective enablement
    into `hooks/list` from source-derived managedness.
    - `config/src/hook_config.rs`, which defines the new `hooks.state`
    representation.
    - `core/src/session/mod.rs`, which rebuilds live hook state after user
    config reloads.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add hooks/list app-server RPC (#19778)
    ## Why
    
    We need a way to list the available hooks to expose via the TUI and App
    so users can view and manage their hooks
    
    ## What
    
    - Adds `hooks/list` for one or more `cwd` values that returns discovered
    hook metadata
    
    ## Stack
    
    1. openai/codex#19705
    2. This PR - openai/codex#19778
    3. openai/codex#19840
    4. openai/codex#19882
    
    ## Review Notes
    
    The generated schema files account for most of the raw diff, these files
    have the core change:
    
    - `hooks/src/engine/discovery.rs` builds the inventory entries during
    hook discovery while leaving runtime handlers focused on execution.
    - `app-server/src/codex_message_processor.rs` wires `hooks/list` into
    the app-server flow for each requested `cwd`.
    - `app-server-protocol/src/protocol/v2.rs` defines the new v2
    request/response payloads exposed on the wire.
    
    ### Core Changes
    
    `core/src/plugins/manager.rs` adds `plugins_for_layer_stack(...)` so
    `skills/list` and `hooks/list`can resolve plugin state for each
    requested `cwd`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Update Codex login success page UX (#20136)
    ## Summary
    
    update the local login success page to match the Codex desktop auth UX
    use theme-aware colors and an inline 20px Codex mark
    keep the actual localhost success page aligned with the browser auth UX
    PR
    
    ## Tests
    
    <img width="1728" height="1117" alt="Screenshot 2026-04-29 at 12 00
    34 PM"
    src="https://github.com/user-attachments/assets/76a40c3f-07c3-452c-97da-e7c43717cd2c"
    />
  • [app-server] type client response payloads (#20050)
    ## Why
    
    `pr17088` adds typed server-originated request/response plumbing, but
    successful client responses are still erased into bare JSON-RPC `result`
    values before app-server can make any typed decision about them.
    
    This precursor PR keeps successful client responses typed until the
    outgoing response seam. It is intentionally limited to
    protocol/app-server plumbing so the analytics behavior change can review
    separately on top.
    
    ## What changed
    
    - Add `ClientResponsePayload` as the pre-serialization client response
    body type.
    - Route app-server successful response paths through the typed payload
    seam while preserving existing handler-local analytics behavior.
    - Keep `InterruptConversation` JSON-RPC-only because it has no
    `ClientResponse` variant.
    - Move the new payload conversion tests into a dedicated protocol test
    module.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server-protocol`
  • [codex-analytics] ingest server requests and responses (#17088)
    ## Why
    
    Codex analytics needs a typed seam for app-server-originated
    request/response traffic so future tool-approval analytics can consume
    those facts without adding bespoke callsite tracking each time. Server
    responses arrive as JSON-RPC `id + result` payloads, so analytics has to
    reconstruct the matching typed response from the original typed request
    while that request context still exists in app-server.
    
    This also puts analytics on the app-server outbound path, which needs to
    avoid keeping the runtime alive during shutdown. The final ownership fix
    keeps the normal strong auth-manager retention in analytics and makes
    the external-auth refresh bridge hold a weak back-reference to
    `OutgoingMessageSender`, breaking the runtime cycle at the bridge
    boundary instead of exposing retention policy through the analytics
    client API.
    
    ## What changed
    
    - Adds typed `ServerRequest` and `ServerResponse` analytics facts, plus
    `AnalyticsEventsClient::track_server_request` and
    `track_server_response`.
    - Renames the existing client-side facts to `ClientRequest` and
    `ClientResponse` so reducers can distinguish client-to-server traffic
    from server-to-client traffic.
    - Adds `ServerRequest::response_from_result`, allowing a stored typed
    request to decode the matching typed server response from a raw JSON-RPC
    result payload.
    - Threads `AnalyticsEventsClient` through `OutgoingMessageSender` and
    records targeted server requests, replayed targeted requests, and
    matching targeted responses with the responding connection id needed for
    correlation.
    - Intentionally leaves broadcast server requests/responses out of
    analytics for now because the current model is per connection, while
    broadcasts fan one logical request out across multiple connections.
    - Breaks the app-server shutdown cycle by storing
    `Weak<OutgoingMessageSender>` in `ExternalAuthRefreshBridge` and
    upgrading it only when an external-auth refresh is actually requested.
    - Keeps reducer ingestion of the new server-side facts as no-ops for
    now; this PR is plumbing for later tool-approval analytics work.
    
    ## Verification
    
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-app-server outgoing_message::tests::`
    - Covers typed-response reconstruction plus the targeted, replayed,
    broadcast-exclusion, and response-attribution analytics paths.
    
    ## Follow-up
    
    This PR intentionally stops at ingestion plumbing, so `ServerRequest`
    and `ServerResponse` facts are still reducer no-ops. Once a follow-up PR
    adds real downstream analytics output for those facts:
    
    - replace the temporary pre-reducer observation seam with reducer tests
    for the emitted event shape;
    - add end-to-end coverage in `app-server/tests/suite/v2/analytics.rs`
    for the real app-server workflow and captured analytics payload;
    - remove the temporary sender-level observer tests added here in favor
    of the real-output coverage above.
    
    ---
    
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17088).
    * #18748
    * #18747
    * #17090
    * #17089
    * #20241
    * #20239
    * __->__ #17088
  • feat: expose provider capability bounds to app server clients (#20049)
    follow up of #19442. The app server now exposes provider-derived bounds
    through a new v2 `modelProvider/read` method. The response reports the
    configured provider map key as `modelProvider` and returns the effective
    capability booleans so clients can align their UI with the same
    provider-owned limits used by core.
  • Support detect and import MCP, Subagents, hooks, commands from external (#19949)
    ## Why
    This PR expands the migration path so Codex can detect and import MCP
    server config, hooks, commands, and subagents configs in a Codex-native
    shape.
    
    ## What changed
    
    - Added a `codex-external-agent-migration` crate that owns conversion
    logic for external-agent MCP servers, hooks, commands, and subagents.
    - Extended the app-server external-agent config detection/import API
    with migration item types for MCP server config, hooks, commands, and
    subagents.
    
    ## Migration strategy
    
    The migration is intentionally conservative: Codex only imports
    external-agent config that can be represented safely in Codex today.
    Unsupported or ambiguous config is skipped instead of being partially
    translated into behavior that may not match the source system.
    
    - **MCP servers**: import supported stdio and HTTP MCP server
    definitions into `mcp_servers`. Disabled servers and servers filtered
    out by source `enabledMcpjsonServers` / `disabledMcpjsonServers` are
    skipped. Project-scoped MCP entries from `.claude.json` are included
    when they match the repo path.
    - **Hooks**: import only supported command hooks into
    `.codex/hooks.json`. Unsupported hook features such as conditional
    groups, async handlers, prompt/http hooks, or unknown fields are
    skipped. Referenced hook scripts are copied into `.codex/hooks/`,
    preserving any existing target scripts.
    - **Commands**: import supported external commands as Codex skills under
    `.agents/skills/source-command-*`. Commands that rely on source runtime
    expansion such as `$ARGUMENTS`, `$1`, `@file` references, shell
    interpolation, or colliding generated names are skipped.
    - **Subagents**: import valid subagent Markdown files into
    `.codex/agents/*.toml` when they have the minimum Codex agent fields.
    Source model names are not migrated, so imported agents keep the user’s
    Codex default model; compatible reasoning effort and sandbox mode are
    migrated when present.
    - **Skills and project guidance**: copy missing skill directories into
    `.agents/skills` and migrate `CLAUDE.md` guidance into `AGENTS.md`,
    rewriting source-agent terminology to Codex terminology where
    appropriate.
    - **Detection details**: detected migration items include lightweight
    details for UI preview, such as MCP server names, hook event names,
    generated command skill names, and subagent names. Import still
    recomputes from disk instead of trusting details as the source of truth.
    
    - Adds focused coverage for the new migration behavior and app-server
    import flow.
    
    ## Verification
    
    - `cargo test -p codex-external-agent-migration`
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-app-server external_agent_config`
    - `just bazel-lock-check`
  • app-server: notify clients of remote-control status changes (#19919)
    ## Why
    
    Remote-control app-server enrollments have both an internal server id
    and the environment id exposed to remote-control clients. App-server
    clients need one current status snapshot that says whether remote
    control is usable and which environment id, if any, is exposed.
    
    A temporary websocket disconnect is not itself an identity change.
    Account changes, stale enrollment invalidation, successful
    re-enrollment, and missing ChatGPT auth are meaningful status changes.
    Disabled remote control remains `disabled` regardless of auth or SQLite
    state. SQLite startup failure disablement and enrollment persistence
    failures are handled in #20068; this PR reports the resulting effective
    status to clients.
    
    ## What changed
    
    - Adds v2 `remoteControl/status/changed` carrying `state` and
    `environmentId`.
    - Adds `RemoteControlConnectionState` values: `disabled`, `connecting`,
    `connected`, and `errored`.
    - Exposes remote-control status updates through `RemoteControlHandle`
    using a Tokio watch channel.
    - Always sends the current remote-control status snapshot to newly
    initialized app-server clients.
    - Broadcasts status changes to initialized app-server clients when state
    or environment id changes.
    - Treats missing ChatGPT auth as an `errored` status while leaving it
    retryable because auth can change at runtime.
    - Clears `environmentId` when enrollment is cleared for account changes,
    auth loss, stale backend invalidation, or disabled remote control.
    - Updates app-server protocol schema fixtures, generated TypeScript,
    app-server README, remote-control tests, and TUI exhaustive notification
    matches.
    
    ## Stack
    
    - Builds on #20068.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server transport::remote_control --lib`
    - `cargo check -p codex-tui`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
  • Discover hooks bundled with plugins (#19705)
    ## Why
    
    Plugins can bundle lifecycle hooks, but Codex previously only discovered
    hooks from user, project, and managed config layers. This adds the
    plugin discovery and runtime plumbing needed for plugin-bundled hooks
    while keeping execution behind the `plugin_hooks` feature flag.
    
    ## What
    
    - Discovers plugin hook sources from each plugin's default
    `hooks/hooks.json`.
    - Supports `plugin.json` manifest `hooks` entries as either relative
    paths or inline hook objects.
    - Plumbs discovered plugin hook sources through plugin loading into the
    hook runtime when `plugin_hooks` is enabled.
    - Marks plugin-originated hook runs as `HookSource::Plugin`.
    - Injects `PLUGIN_ROOT` and `CLAUDE_PLUGIN_ROOT` into plugin hook
    command environments.
    - Updates generated schemas and hook source metadata for the plugin hook
    source.
    
    ## Stack
    
    1. This PR - openai/codex#19705
    2. openai/codex#19778
    3. openai/codex#19840
    4. openai/codex#19882
    
    ## Reviewer Notes
    
    - Core logic is in `codex-rs/core-plugins/src/loader.rs` and
    `codex-rs/hooks/src/engine/discovery.rs`
    - Moved existing / adding new tests to
    `codex-rs/core-plugins/src/loader_tests.rs` hence the large diff there
    - Otherwise mostly plumbing and minor schema updates
    
    ### Core Changes
    
    The `codex-rs/core` changes are limited to wiring plugin hook support
    into existing core flows:
    
    - `core/src/session/session.rs` conditionally pulls effective plugin
    hook sources and plugin hook load warnings from `PluginsManager` when
    `plugin_hooks` is enabled, then passes them into `HooksConfig`.
    - `core/src/hook_runtime.rs` adds the `plugin` metric tag for
    `HookSource::Plugin`.
    - `core/config.schema.json` picks up the new `plugin_hooks` feature
    flag, and `core/src/plugins/manager_tests.rs` updates fixtures for the
    added plugin hook fields.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • app-server: run initialized rpcs with keyed serialization (#17373)
    ## Why
    
    Initialized app-server RPCs no longer need to bottleneck behind one
    request processor path. Running them concurrently improves
    responsiveness, but several request families still mutate shared state
    or depend on ordered side effects. Those stateful families need an
    auditable serialization contract so concurrency does not reorder thread,
    config, auth, command, watcher, MCP, or similar state transitions.
    
    This PR keeps that boundary explicit: stateful work is serialized by the
    smallest useful key, while intentionally read-only or externally
    concurrent work remains unkeyed. In particular, `thread/list` and
    `thread/turns/list` explicitly have no serialization because they
    primarily read append-only rollout storage and should continue to be
    served concurrently.
    
    ## What changed
    
    - Adds `ClientRequest::serialization_scope()` in `app-server-protocol`
    and requires every client request definition to declare its
    serialization behavior.
    - Introduces keyed request scopes for thread, thread path, command exec
    process, fuzzy search session, fs watch, MCP OAuth, and global state
    buckets such as config, account auth, memory, and device keys.
    - Routes initialized app-server RPCs through per-key FIFO serialization
    while allowing unkeyed initialized requests to run concurrently.
    - Cancels in-flight initialized RPC work when the connection disconnects
    or the app-server exits so spawned request tasks do not outlive their
    session.
    - Adds focused coverage for representative keyed and unkeyed
    serialization scopes, including explicitly concurrent
    `thread/turns/list` behavior.
    
    ## Validation
    
    - Added protocol tests for representative keyed serialization scopes and
    intentionally unkeyed request families.
    - Added app-server request serialization tests covering per-key FIFO
    behavior, concurrent unkeyed execution, disconnect shutdown, and config
    read-after-write ordering.
    - Local focused protocol validation after the latest rebase is currently
    blocked by packageproxy failing to resolve locked `rustls-webpki
    0.103.13`; CI is expected to provide the full validation signal.
  • External agent session support (#19895)
    ## Summary
    
    This extends external agent detection/import beyond config artifacts so
    Codex can detect recent sessions files from the external agent home and
    import them into Codex rollout history.
    
    ## What changed
    
    - Added a focused `external_agent_sessions` module for:
      - session discovery
      - source-record parsing
      - rollout construction
      - import ledger tracking
    - Wired session detection/import into the app-server external agent
    config API.
    - Added compaction handling so large imported sessions can be resumed
    safely before the first follow-up turn.
    
    ## Testing
    
    Added coverage for:
    - recent-session detection
    - custom-title handling
    - recency filtering
    - dedupe and re-detect-after-source-change behavior
    - visible imported turn construction
    - backward-compatible import payload deserialization
    - end-to-end RPC import flow
    - rejection of undetected session paths
    - repeat-import behavior
    - large-session compaction before first follow-up
    
    Ran:
    - `cargo test -p codex-app-server external_agent_config_import_ --test
    all`
  • feat: split memories part 2 (#19860)
    Keep extracting memories out of core and moving the write trigger in the
    app-server
    This is temporary and it should move at the client level as a follow-up
    This makes core fully independant from `codex-memories-write`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add remote plugin uninstall API (#19456)
    ## Summary
    - Adds the remote `plugin/uninstall` request form using required
    `pluginId` plus optional `remoteMarketplaceName`, while preserving local
    `pluginId` uninstall.
    - Adds `codex_core_plugins::remote::uninstall_remote_plugin` for the
    deployed ChatGPT plugin backend uninstall path and validates the backend
    returns the same id with `enabled: false`.
    - Routes app-server remote uninstall through feature checks, remote
    plugin id validation, backend mutation, local downloaded cache deletion,
    cache clearing, docs, and regenerated protocol schemas.
    
    ## Tests
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol
    plugin_uninstall_params_serialization_omits_force_remote_sync`
    - `cargo test -p codex-app-server plugin_uninstall --test all`
    - `cargo test -p codex-app-server plugin_uninstall`
    - `cargo build -p codex-cli`
    - `CODEX_BIN=/Users/xli/code/codex/codex-rs/target/debug/codex python3
    /Users/xli/.codex/skills/xli-test-marketplace-api/scripts/run_marketplace_api_matrix.py`
    (44 pass / 0 fail)
    - `just fix -p codex-app-server-protocol -p codex-app-server -p
    codex-tui`
    - `just fix -p codex-app-server`
  • app-server-protocol: mark permission profiles experimental (#19899)
    ## Why
    
    `PermissionProfile` is now the canonical internal permissions
    representation, but the app-server wire shape is still intentionally
    unstable while the migration continues. Stable app-server clients should
    not see or generate code for these fields until the wire format settles.
    
    ## What changed
    
    - Marks every app-server v2 field that sends `PermissionProfile` as
    experimental, including `command/exec`, `thread/start`, `thread/resume`,
    `thread/fork`, and `turn/start` request/response payloads.
    - Enables per-field experimental inspection for `command/exec`, so
    `permissionProfile` is gated without making the entire method
    experimental.
    - Fixes the generated TypeScript schema filter to be comment-aware. The
    previous scanner treated apostrophes inside doc comments as string
    delimiters, so some experimental fields leaked into stable TypeScript
    even though stable JSON was filtered correctly.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19899).
    * #19900
    * __->__ #19899
  • Remove ghost snapshots (#19481)
    ## Summary
    - Remove `ghost_snapshot` / `GhostCommit` from the Responses API surface
    and generated SDK/schema artifacts.
    - Keep legacy config loading compatible, but make undo a no-op that
    reports the feature is unavailable.
    - Clean up core history, compaction, telemetry, rollout, and tests to
    stop carrying ghost snapshot items.
    
    ## Testing
    - Unit tests passed for `codex-protocol`, `codex-core` targeted undo and
    compaction flows, `codex-rollout`, and `codex-app-server-protocol`.
    - Regenerated config and app-server schemas plus Python SDK artifacts
    and verified they match the checked-in outputs.
  • permissions: remove cwd special path (#19841)
    ## Why
    
    The experimental `PermissionProfile` API had both `:cwd` and
    `:project_roots` special filesystem paths, which made the permission
    root ambiguous. This PR removes the unstable `current_working_directory`
    special path before the permissions API is stabilized, so callers use
    `:project_roots` for symbolic project-root access.
    
    ## What changed
    
    - Removes `FileSystemSpecialPath::CurrentWorkingDirectory` from protocol
    and app-server protocol models, plus regenerated app-server
    JSON/TypeScript schemas.
    - Replaces internal `:cwd` permission entries with `:project_roots`
    entries.
    - Keeps the existing cwd-update behavior for legacy-shaped
    workspace-write profiles, while removing the deleted
    `CurrentWorkingDirectory` case from that compatibility path.
    - Keeps `PermissionProfile::workspace_write()` as the reusable symbolic
    workspace-write helper, with docs noting that `:project_roots` entries
    resolve at enforcement time.
    - Updates app-server docs/examples and approval UI labeling to stop
    advertising `:cwd` as a permission token.
    
    ## Compatibility
    
    Persisted rollout items may contain the old
    `{"kind":"current_working_directory"}` tag from earlier experimental
    `permissionProfile` snapshots. This PR keeps that tag as a
    deserialize-only alias for `ProjectRoots { subpath: None }`, while
    continuing to serialize only the new `project_roots` tag.
    
    ## Follow-up
    
    This PR intentionally does not introduce an explicit project-root set on
    `SessionConfiguration` or runtime sandbox resolution. Today, the
    resolver still uses the active cwd as the single implicit project root.
    A follow-up should model project roots separately from tool cwd so
    `:project_roots` entries can resolve against the configured project
    roots, and resolve to no entries when there are no project roots.
    
    ## Verification
    
    - `cargo test -p codex-protocol permissions:: --lib`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-sandboxing -p codex-exec-server --lib`
    - `cargo test -p codex-core session_configuration_apply_ --lib`
    - `cargo test -p codex-app-server
    command_exec_permission_profile_project_roots_use_command_cwd --test
    all`
    - `cargo test -p codex-tui
    thread_read_session_state_does_not_reuse_primary_permission_profile
    --lib`
    - `cargo test -p codex-tui
    preset_matching_accepts_workspace_write_with_extra_roots --lib`
    - `cargo test -p codex-config --lib`
  • Add goal app-server API (2 / 5) (#18074)
    Adds the app-server v2 goal API on top of the persisted goal state from
    PR 1.
    
    ## Why
    
    Clients need a stable app-server surface for reading and controlling
    materialized thread goals before the model tools and TUI can use them.
    Goal changes also need to be observable by app-server clients, including
    clients that resume an existing thread.
    
    ## What changed
    
    - Added v2 `thread/goal/get`, `thread/goal/set`, and `thread/goal/clear`
    RPCs for materialized threads.
    - Added `thread/goal/updated` and `thread/goal/cleared` notifications so
    clients can keep local goal state in sync.
    - Added resume/snapshot wiring so reconnecting clients see the current
    goal state for a thread.
    - Added app-server handlers that reconcile persisted rollout state
    before direct goal mutations.
    - Updated the app-server README plus generated JSON and TypeScript
    schema fixtures for the new API surface.
    
    ## Verification
    
    - Added app-server v2 coverage for goal get/set/clear behavior,
    notification emission, resume snapshots, and non-local thread-store
    interactions.
  • permissions: remove legacy read-only access modes (#19449)
    ## Why
    
    `ReadOnlyAccess` was a transitional legacy shape on `SandboxPolicy`:
    `FullAccess` meant the historical read-only/workspace-write modes could
    read the full filesystem, while `Restricted` tried to carry partial
    readable roots. The partial-read model now belongs in
    `FileSystemSandboxPolicy` and `PermissionProfile`, so keeping it on
    `SandboxPolicy` makes every legacy projection reintroduce lossy
    read-root bookkeeping and creates unnecessary noise in the rest of the
    permissions migration.
    
    This PR makes the legacy policy model narrower and explicit:
    `SandboxPolicy::ReadOnly` and `SandboxPolicy::WorkspaceWrite` represent
    the old full-read sandbox modes only. Split readable roots, deny-read
    globs, and platform-default/minimal read behavior stay in the runtime
    permissions model.
    
    ## What changed
    
    - Removes `ReadOnlyAccess` from
    `codex_protocol::protocol::SandboxPolicy`, including the generated
    `access` and `readOnlyAccess` API fields.
    - Updates legacy policy/profile conversions so restricted filesystem
    reads are represented only by `FileSystemSandboxPolicy` /
    `PermissionProfile` entries.
    - Keeps app-server v2 compatible with legacy `fullAccess` read-access
    payloads by accepting and ignoring that no-op shape, while rejecting
    legacy `restricted` read-access payloads instead of silently widening
    them to full-read legacy policies.
    - Carries Windows sandbox platform-default read behavior with an
    explicit override flag instead of depending on
    `ReadOnlyAccess::Restricted`.
    - Refreshes generated app-server schema/types and updates tests/docs for
    the simplified legacy policy shape.
    
    ## Verification
    
    - `cargo check -p codex-app-server-protocol --tests`
    - `cargo check -p codex-windows-sandbox --tests`
    - `cargo test -p codex-app-server-protocol sandbox_policy_`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19449).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19391
    * __->__ #19449
  • permissions: make legacy profile conversion cwd-free (#19414)
    ## Why
    
    The profile conversion path still required a `cwd` even when it was only
    translating a legacy `SandboxPolicy` into a `PermissionProfile`. That
    made profile producers invent an ambient `cwd`, which is exactly the
    anchoring we are trying to remove from permission-profile data. A legacy
    workspace-write policy can be represented symbolically instead: `:cwd =
    write` plus read-only `:project_roots` metadata subpaths.
    
    This PR creates that cwd-free base so the rest of the stack can stop
    threading cwd through profile construction. Callers that actually need a
    concrete runtime filesystem policy for a specific cwd still have an
    explicitly named cwd-bound conversion.
    
    ## What Changed
    
    - `PermissionProfile::from_legacy_sandbox_policy` now takes only
    `&SandboxPolicy`.
    - `FileSystemSandboxPolicy::from_legacy_sandbox_policy` is now the
    symbolic, cwd-free projection for profiles.
    - The old concrete projection is retained as
    `FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd` for
    runtime/boundary code that must materialize legacy cwd behavior.
    - Workspace-write profiles preserve `CurrentWorkingDirectory` and
    `ProjectRoots` special entries instead of materializing cwd into
    absolute paths.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-exec -p
    codex-exec-server -p codex-tui -p codex-sandboxing -p
    codex-linux-sandbox -p codex-analytics --tests`
    - `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-exec -p codex-exec-server -p codex-tui -p
    codex-sandboxing -p codex-linux-sandbox -p codex-analytics`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19414).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19391
    * __->__ #19414