Commit Graph

1412 Commits

  • [codex] Add optional IDs to response items (#28812)
    ## Why
    
    `ResponseItem` variants do not have a consistent internal ID shape: some
    variants carry required IDs, some carry optional IDs, and some cannot
    represent an ID at all. The existing fields also use inconsistent serde,
    TypeScript, and JSON-schema annotations. A single enum-level access path
    is needed before history recording can assign and retain IDs.
    
    This PR establishes that internal model only. It intentionally does not
    generate or serialize IDs; allocation and wire persistence are isolated
    in the stacked follow-up.
    
    ## What changed
    
    - Give every concrete `ResponseItem` variant an `Option<String>` ID
    field.
    - Apply the same internal-only annotations to every ID field:
    `#[serde(default, skip_serializing)]`, `#[ts(skip)]`, and
    `#[schemars(skip)]`.
    - Add `ResponseItem::id()` and `ResponseItem::set_id()` as the shared
    accessors.
    - Preserve IDs when history items are rewritten for truncation.
    - Adapt consumers that previously assumed reasoning and image-generation
    IDs were required.
    - Regenerate app-server schemas so the hidden fields are represented
    consistently.
    
    The serde catch-all `ResponseItem::Other` remains ID-less because it
    must remain a unit variant.
    
    ## Test plan
    
    - `cargo check --tests -p codex-core -p codex-api -p codex-rollout-trace
    -p codex-image-generation-extension`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-api -p codex-rollout-trace -p
    codex-image-generation-extension`
    - `just test -p codex-core event_mapping`
  • unified-exec: preserve PathUri through exec-server (#28681)
    ## Why
    
    It should be possible for app-server to handle "foreign" OS paths in
    unified_exec working directories, allowing e.g. a Linux app-server to
    run processes on e.g. a Windows exec-server.
    
    ## What
    
    Convert the core unified_exec cwd values to use `PathUri`.
    
    Adds fallible path conversion in several places to try to minimize the
    scope of this change. The only time this change suppresses errors from
    converting `PathUri` to an `AbsolutePathBuf` is when the turn is
    configured with no sandboxing at all to allow us to make progress
    testing without sandboxing.
    
    Future changes to apply_patch and sandboxing will clean up these error
    paths.
    
    A tool's cwd is resolved from joining a model-provided workdir to the
    environment's cwd. When using `AbsolutePathBuf::join()`, an
    absolute-path workdir would overwrite the environment's cwd and we would
    resolve permissions/sandboxing against the model-provided path. This
    change extends `PathUri::join()` to also treat an absolute rhs as an
    override of the base/lhs.
    
    This also removes some coverage from the remove_env_windows tests until
    a follow-up converts foreign paths in command exec events correctly.
    
    ## Breaking Changes
    
    When using `AbsolutePathBuf::join()` for workdir resolution, we ended up
    resolving tilde-prefixed paths against the app-server's `$HOME`, e.g.
    `~/foo/bar` becomes `/home/anp/foo/bar`. It's difficult to do this with
    `PathUri` joining, so after offline discussion this PR no longer
    implements it.
    
    A quick check of some power users' rollouts suggests that models don't
    actually generate home-prefixed absolute working directories for their
    spawns, so this shouldn't have any real blast radius.
  • [ez][codex-rs] Support apps._default.default_tools_approval_mode (#27965)
    [from codex]
    
    ## Summary
    
    - add `default_tools_approval_mode` to `[apps._default]` and expose it
    through app-server v2 `config/read`
    - apply it after managed, per-tool, and per-app approval settings,
    before the built-in `auto` fallback
    - document the precedence, regenerate config/app-server schemas, and add
    unit plus end-to-end approval coverage
    
    ## Configuration
    
    ```toml
    [apps._default]
    default_tools_approval_mode = "prompt"
    ```
    
    The effective precedence is managed requirements, tool-specific
    `approval_mode`, app-specific `default_tools_approval_mode`,
    `apps._default.default_tools_approval_mode`, then `auto`.
    
    ## Test plan
    
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just write-app-server-schema --experimental`
    - `just test -p codex-core app_tool_policy`
    - `just test -p codex-core mcp_turn_metadata`
    - `just test -p codex-config`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server config_read_includes_apps`
    - `just fix -p codex-config -p codex-core -p codex-app-server-protocol
    -p codex-app-server`
    - `just fmt`
  • app-server: keep the model cache warm (#28699)
    ## Why
    
    The app server is long-lived, but its shared model cache otherwise
    refreshes only when a caller needs it. Once the five-minute cache
    expires, starting a thread or calling `model/list` can wait for
    `/models` on the request path.
    
    Refresh the cache in the background before it expires so foreground
    callers normally use fresh local state.
    
    ## What changed
    
    - Start an app-server worker that refreshes models immediately and then
    every three minutes using the existing models-manager API.
    - Hold only a weak reference to the models manager between refreshes, so
    the worker does not extend its lifetime.
    - Stop scheduling refreshes when the app-server lifecycle handle is shut
    down or dropped. A refresh already in progress is allowed to finish.
    - Adjust affected app-server test fixtures to distinguish the background
    `/models` probe from the connection they are testing.
    
    The existing models-manager cache, refresh strategies, auth handling,
    ETag behavior, and concurrency semantics are unchanged.
    
    ## Testing
    
    -
    `models_refresh_worker::tests::refreshes_immediately_periodically_and_stops_when_dropped`
    -
    `suite::v2::remote_control::listen_off_honors_persisted_remote_control_enable`
    -
    `suite::v2::attestation::attestation_generate_round_trip_adds_header_to_responses_websocket_handshake`
  • Add join key for MAv2 inter-agent messages (#28561)
    ## Summary
    This keeps inter-agent communication on the existing raw response item
    path and adds a join key for MAv2 tool calls.
    
    MAv2 `spawn_agent`, `send_message`, and `followup_task` now stamp the
    originating tool call id into `ResponseItemMetadata.source_call_id` on
    the raw `ResponseItem::AgentMessage`. App-server clients can join that
    raw item back to the existing tool/activity event by call id, while
    using the raw agent message's existing sender, receiver, and content
    fields.
    
    No new app-server `ThreadItem` or notification type is added.
    
    ## Tests
    - `just fmt`
    - `just write-app-server-schema`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core
    multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_path`
    - `just test -p codex-core
    multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn`
    - `just fix -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core`
  • [codex] Persist built-in image results reported as generating (#28656)
    ## Why
    
    #27920 stopped persisting image-generation items unless their status was
    `completed`, preventing failed standalone extension items with empty
    results from being saved. Built-in image generation can instead emit a
    terminal `response.output_item.done` containing a complete base64 PNG
    while the item status remains `generating`. In that case, app-server
    emits no `savedPath`, so Codex Apps can render the inline image but
    cannot expose a file artifact.
    
    ## What changed
    
    - Persist image-generation items whenever `result` contains image data.
    Failed terminal items still have empty results and remain unpersisted.
    - Update the existing built-in image-generation integration test to
    cover a terminal `generating` item and verify both `saved_path` and the
    written PNG bytes.
    
    ## Validation
    
    - Confirmed with a raw built-in websocket trace: the image progressed
    through `in_progress`, `generating`, and `partial_image`, then emitted
    one `response.output_item.done` with `status: "generating"` and a
    complete PNG result.
    - `just test -p codex-core builtin_image_generation_call_persisted` is
    currently blocked before test execution by a pre-existing compile error
    in `thread-store/src/thread_metadata_sync.rs:171`.
  • [codex] Test code-mode variable truncation (#28471)
    ## Summary
    
    Code mode has two separate truncation points: the nested tool result
    returned to JavaScript and the code-mode output later recorded for the
    model. These tests now verify those behaviors independently.
    
    - Report whether `result.output` was truncated before printing it.
    - Verify omitted or sufficiently large nested limits produce `Variable
    truncated: False`, while allowing the printed value to be truncated
    downstream.
    - Verify an explicit nested limit produces `Variable truncated: True`
    when the command output exceeds it.
    - Use a token-policy model fixture so downstream truncation is visible
    as `…N tokens truncated…`.
    - Align the explicit nested-truncation expectation with the warning
    header.
    
    This PR changes test coverage only; runtime truncation behavior is
    unchanged.
    
    ## Validation
    
    - `env -u CODEX_SANDBOX_NETWORK_DISABLED RUST_MIN_STACK=8388608 cargo
    test -p codex-core --test all code_mode_exec -- --nocapture` (8 passed)
  • [codex] core: restore absolute turn context cwd (#28629)
    ## Why
    
    #28152 jumped the gun on moving the rollout format to store URIs, and
    would likely break compat with some features that don't go through the
    same types as the core logic.
    
    ## What
    
    Make `TurnContextItem.cwd` an `AbsolutePathBuf` again, remove test added
    for `PathUri` serialization in rollouts. Also drops a bunch of error
    paths that are no longer needed.
  • [codex] [4/4] Simplify recommended plugin install schema (#28403)
    ## Summary
    - Simplify recommendation-context `request_plugin_install` arguments to
    `plugin_id` and `suggest_reason`.
    - Derive plugin type and install action from the matched candidate while
    preserving Codex-owned elicitation metadata.
    - Keep the legacy list-backed schema unchanged and accept resumed calls
    that still use `tool_id`.
    
    ## Stack
    - #28399
    - #28400
    - #27704
    - This PR
    
    ## Validation
    - `just test -p codex-tools -p codex-core request_plugin_install` (25
    passed)
    - `just fix -p codex-tools -p codex-core`
    - `just fmt`
    - `git diff --check`
  • core: render remote environment cwd natively (#28152)
    ## Why
    
    Model-visible `<environment_context>` should match the environment of
    the executor, not of the app server.
    
    Stacked on #28146.
    
    ## What
    
    - Keep selected environment cwd values as `PathUri` while building
    environment context.
    - Render cwd text using the path convention represented by the URI, with
    the canonical URI as a fallback.
    - Preserve compatibility with legacy `TurnContextItem.cwd` values when
    reconstructing and diffing context.
    - Extend the Wine-backed remote Windows test to assert that the model
    sees `powershell` and `C:\windows`.
  • [codex] [3/4] Activate endpoint plugin recommendations (#27704)
    Summary\n- Await endpoint recommendation selection while constructing
    each authenticated turn, removing the first-turn cache race.\n- Snapshot
    and filter endpoint candidates once per turn, then use that same set for
    the bounded contextual user fragment, tool exposure, and exact install
    validation.\n- Keep recommendation selection ephemeral: do not persist
    recommendation state in or gate resumed threads on prior context.\n-
    Hide the legacy list tool in endpoint mode and preserve legacy discovery
    unchanged when the endpoint is disabled or unavailable.\n- Keep remote
    plugin and connector app identities out of model-visible context and
    attach them only to Codex-owned elicitation metadata.\n\nStack\n- 3/4,
    based on #28400.\n- Endpoint client and cache: #28399.\n- Generalized
    suggestion presentation: #28400.\n- Install-schema follow-up:
    #28403.\n\nValidation\n- \n- \n- \n- \n- Full : 2,649 passed and 88
    environment-dependent tests failed because this sandbox cannot write ,
    nest Seatbelt, or locate auxiliary test binaries.
  • app-server: preserve target-native environment cwd (#28146)
    ## Why
    
    app-server may run on a different OS from the selected exec-server
    environment. Parsing that environment’s cwd with the Codex host’s path
    rules prevents thread startup.
    
    ## What
    
    Carry environment cwd values as `LegacyAppPathString` at the app-server
    boundary and `PathUri` internally. Existing tool-call schemas and
    relative-path behavior stay host-native; remaining local-only consumers
    convert explicitly and leave follow-up TODOs.
    
    The Wine integration test verifies app-server can start a thread and
    complete an ordinary turn with a Windows environment cwd from Linux.
    
    ## Validation
    
    - `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test
    --test_output=errors`
    - focused app-server environment-selection and protocol schema tests
    - scoped Clippy for `codex-core` and `codex-app-server-protocol`
  • Clarify model-generated and legacy app path types (#28577)
    ## Why
    
    `ApiPathString` kind of implies that it can be used anywhere we pull a
    path out of JSON, but it's not really appropriate for tool arguments
    when the model might generate relative paths.
    
    Prefer `String` for model-generated paths and we can handle the
    conversion per feature for now and define a shared abstraction later if
    it makes sense.
    
    # What
    
    Rename `ApiPathString` to `AppLegacyPathString` to clarify its role.
    
    Expand the `path-types` skill to tell the model to leave tool args as
    bare strings.
  • [codex] test exec relative additional permissions (#28587)
    ## Why
    
    Review caught some would-be regressions in changes to unified_exec that
    weren't surfaced in CI.
    
    ## What
    
    Add coverage for requesting permissions through unified exec when there
    are additional permissions. Previously this flow was only tested against
    shell_command.
  • code-mode: extend test coverage to lock in cell lifecycle (#28468)
    This PR establishes the intended behavior as an executable contract
    before a refactor of the cell runtime begins. It also fixes cases where
    a second observer or termination request could replace an existing
    response channel and leave the original caller unresolved.
    
    ### Behavior codified
    - A cell can yield output and subsequently resume to completion.
    - A caller can run a cell until it has no immediately runnable work,
    receive its accumulated output and outstanding tool-call IDs, and then
    resume the same cell when the awaited work is available.
    - Each cell admits one active observer:
       - a second observer receives an explicit busy error
       - the existing observer remains registered and is not displaced
    - A natural result (conclusion of the js module) that has already
    reached the cell controller wins over a later termination request.
    - Otherwise, termination preempts execution and resolves both:
      - the active observer, if present
      - the caller requesting termination
    - Repeated termination requests are rejected while termination is
    already in progress.
    - Terminal responses are sent only after outstanding callback work has
    been handled:
    - natural completion drains notifications and cancels outstanding tool
    calls
    - termination cancels and drains both notification and tool callbacks.
    - Cell removal and cell_closed notification happen after callback
    cleanup
  • [codex] re-enable absolute workdir integration test (#28581)
    ## Why
    
    In #28146 I missed the invariant that an absolute `exec_command` workdir
    must override the environment cwd. The existing integration test would
    have caught that regression, but it was ignored as flaky.
    
    ## What
    
    Re-enable `unified_exec_respects_workdir_override`.
    
    ## Validation
    
    `just test -p codex-core unified_exec_respects_workdir_override`
  • [codex] Route MCP file uploads through environment filesystem (#27923)
    ## Why
    
    Codex Apps tools can mark arguments with `openai/fileParams`, but the
    execution path resolved and opened those files directly on the host.
    That bypassed the selected turn environment and prevented annotated file
    arguments from working with remote environments.
    
    ## What changed
    
    - resolve annotated file arguments against the primary turn environment
    - read file metadata and contents through that environment's sandboxed
    `ExecutorFileSystem`
    - reject files over the 512 MiB limit from metadata before reading or
    transferring them
    - retain the buffered upload-size check as defense in depth
    - make the OpenAI upload API accept a filename and buffered contents
    instead of owning local filesystem access
    - describe the model-visible argument as a path in the primary
    environment
    
    This builds on #27927, which added `size` to internal filesystem
    metadata.
    
    ## Testing
    
    - `just test -p codex-api upload_openai_file_returns_canonical_uri`
    - `just test -p codex-mcp
    tool_with_model_visible_input_schema_masks_file_params`
    - `just test -p codex-core mcp_openai_file`
    - `just test -p codex-core
    codex_apps_file_params_upload_environment_files_before_mcp_tool_call`
  • [codex] Warn clearly when code mode output is truncated (#28467)
    ## Summary
    
    - make `formatted_truncate_text` prepend `Warning: truncated output
    (original token count: N)` above the existing `Total output lines`
    header
    - update direct formatter, unified-exec, user-shell, and code-mode
    expectations
    - add core unit coverage that runs in Bazel without requiring the
    skipped V8-backed code-mode integration suite
    
    ## Validation
    
    - `cargo test -p codex-utils-output-truncation -- --nocapture` (17
    passed)
    - `cargo test -p codex-core --lib
    truncated_text_output_starts_with_warning -- --nocapture`
    - `cargo test -p codex-core --test all
    clamps_model_requested_max_output_tokens_to_policy -- --nocapture` (2
    passed)
    - `cargo test -p codex-core --test all
    unified_exec_formats_large_output_summary -- --nocapture`
    - `cargo test -p codex-core --test all
    user_shell_command_output_is_truncated_in_history -- --nocapture`
    - Bazel CI exercises the shared formatter and downstream integration
    expectations
  • core: surface terminal subagent errors to parent agents (#28375)
    ## Why
    
    When a subagent exhausts its retries, it emits an `Error`, but the
    generic task lifecycle then emits `TurnComplete(None)`. That completion
    used to overwrite the subagent's `Errored` status with
    `Completed(None)`, so the parent received an empty completion
    notification.
    
    This made a failed child look indistinguishable from a child that
    completed without an answer. In unattended or long-running multi-agent
    work, the root could silently continue without knowing that delegated
    work failed or how to restart it.
    
    ## Behavior
    
    Before, a terminal stream failure was reduced to an empty completion:
    
    ```text
    <subagent_notification>
    {"agent_path":"/root/worker","status":{"completed":null}}
    </subagent_notification>
    ```
    
    Now the parent receives the actual terminal error, bounded to 1,000
    tokens, together with an actionable recovery hint:
    
    ```text
    <subagent_notification>
    {
      "agent_path": "/root/worker",
      "status": {
        "errored": "stream disconnected before completion: stream closed before response.completed"
      },
      "next_action": "This agent's turn failed. If you still need this agent, use `followup_task` to give it another task."
    }
    </subagent_notification>
    ```
    
    The notification remains queue-only: it does not wake the root or replay
    the failed request. The root sees it at the next sampling boundary and
    can use `followup_task` to start a new turn for that agent.
    
    ## What changed
    
    - Added terminal-error precedence to the [agent status
    reducer](https://github.com/openai/codex/blob/e95fcfe2bb6a02f1a75650afa20048859f556511/codex-rs/core/src/agent/status.rs#L23-L34),
    so a closing `TurnComplete` cannot erase an immediately preceding
    `Errored` status.
    - Made MultiAgentV2 completion forwarding use the retained session
    status instead of re-deriving `Completed(None)` from the final event.
    - Extended the [subagent notification
    fragment](https://github.com/openai/codex/blob/e95fcfe2bb6a02f1a75650afa20048859f556511/codex-rs/core/src/context/subagent_notification.rs#L6-L60)
    with a `next_action` for terminal errors and a hard cap on model-visible
    error text.
    - Kept successful completions and interrupted turns unchanged.
    
    ## Verification
    
    - Added a status-reducer test proving that `Errored` survives the
    trailing `TurnComplete`.
    - Added an integration test that exhausts a subagent's stream retries
    and verifies the exact `agent_message` delivered to the parent,
    including the error and `followup_task` guidance.
    - Re-ran the existing successful-completion and interrupted-turn
    notification tests.
  • [tests] Keep Apps out of generic core test harness (#28508)
    ## Summary
    
    - disable the stable Apps feature in the generic `test_codex()`
    integration-test harness
    - keep Apps-specific tests explicit: their builders re-enable Apps and
    point it at a local mock server
    
    ## Why
    
    Generic tests that use dummy ChatGPT auth were also enabling the
    host-owned `codex_apps` MCP server. That made unrelated tests contact
    `chatgpt.com` and wait for MCP startup, causing the Bazel timeouts
    observed on #28368.
    
    The generic harness should be hermetic and should not start an external
    service that the test did not request. This is test-only; production
    Apps behavior is unchanged. The broader optional-MCP startup behavior is
    being handled separately in #28407.
    
    ## Testing
    
    - `just test -p codex-core -E
    'test(pre_sampling_compact_runs_when_comp_hash_changes) |
    test(model_switch_to_smaller_model_updates_token_context_window) |
    test(codex_apps_file_params_upload_local_paths_before_mcp_tool_call)'`
    - `just fix -p codex-core`
    - `just fmt`
  • feat: render typed envelopes for multi-agent v2 messages (#28368)
    ## Why
    
    Multi-agent v2 messages need a consistent, model-visible envelope that
    identifies what kind of interaction occurred, who sent it, and which
    agent it targets. Previously, encrypted deliveries exposed only
    `encrypted_content`, while child completion used the legacy
    `<subagent_notification>` shape. That meant the client could not
    consistently present `NEW_TASK`, `MESSAGE`, and `FINAL_ANSWER` using the
    same format.
    
    This change adds the routing envelope as plaintext while keeping task
    and message payloads encrypted. No new Responses API field is required:
    an encrypted delivery is represented as an `input_text` header
    immediately followed by its existing `encrypted_content` item.
    
    Every envelope now follows this shape:
    
    ```text
    Message Type: <NEW_TASK | MESSAGE | FINAL_ANSWER>
    Task name: <recipient agent path>
    Sender: <author agent path>
    Payload:
    <message payload>
    ```
    
    ## Message types
    
    ### `NEW_TASK`
    
    `NEW_TASK` is used when the recipient should begin a new turn, including
    an initial `spawn_agent` task and a later `followup_task`.
    
    For a root agent spawning `/root/worker`, the request contains a
    plaintext envelope followed by the encrypted task:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root",
      "recipient": "/root/worker",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: NEW_TASK\nTask name: /root/worker\nSender: /root\nPayload:\n"
        },
        {
          "type": "encrypted_content",
          "encrypted_content": "<encrypted task payload>"
        }
      ]
    }
    ```
    
    Conceptually, the model receives:
    
    ```text
    Message Type: NEW_TASK
    Task name: /root/worker
    Sender: /root
    Payload:
    Review the authentication changes and report any regressions.
    ```
    
    ### `MESSAGE`
    
    `MESSAGE` is used for a queued `send_message` delivery. It communicates
    with an existing agent without starting a new turn.
    
    For `/root/worker` reporting progress to the root agent, the request
    contains:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root/worker",
      "recipient": "/root",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: MESSAGE\nTask name: /root\nSender: /root/worker\nPayload:\n"
        },
        {
          "type": "encrypted_content",
          "encrypted_content": "<encrypted message payload>"
        }
      ]
    }
    ```
    
    Conceptually, the model receives:
    
    ```text
    Message Type: MESSAGE
    Task name: /root
    Sender: /root/worker
    Payload:
    The protocol tests pass; I am checking the resume path now.
    ```
    
    ### `FINAL_ANSWER`
    
    `FINAL_ANSWER` is emitted when a child agent reaches a terminal state
    and reports its result to its parent. Completion payloads are already
    available locally, so the complete envelope is represented as plaintext
    rather than as a plaintext header plus encrypted content.
    
    For `/root/worker` completing work for the root agent, the request
    contains:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root/worker",
      "recipient": "/root",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: FINAL_ANSWER\nTask name: /root\nSender: /root/worker\nPayload:\nNo regressions found."
        }
      ]
    }
    ```
    
    The model-visible form is:
    
    ```text
    Message Type: FINAL_ANSWER
    Task name: /root
    Sender: /root/worker
    Payload:
    No regressions found.
    ```
    
    Errored, shut down, and missing agents also use `FINAL_ANSWER`, with a
    terminal-status description in the payload.
    
    ## What changed
    
    - Render `NEW_TASK` or `MESSAGE` in
    `InterAgentCommunication::to_model_input_item`, based on whether the
    encrypted delivery starts a turn.
    - Replace the multi-agent v2 `<subagent_notification>` completion
    payload with a model-visible `FINAL_ANSWER` envelope.
    - Document `Task name`, `Sender`, and `Payload` consistently in the
    multi-agent developer instructions.
    - Prevent local-only history projections from treating an encrypted
    message's plaintext header as the complete assistant message.
    - Preserve rollout-trace interaction edges when an agent message
    contains both plaintext and encrypted content.
    
    Legacy multi-agent behavior remains unchanged.
    
    ## Verification
    
    - `just test -p codex-protocol`
    - `just test -p codex-rollout-trace`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-core
    encrypted_multi_agent_v2_spawn_sends_agent_message_to_child`
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core
    multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn`
    - `just test -p codex-core
    multi_agent_v2_completion_queues_message_for_direct_parent`
  • [codex] Use local environment for user shell commands (#28163)
    ## Why
    
    User shell commands still read the legacy turn cwd and session shell
    even though execution context is now owned by selected turn
    environments. App-server also defines `thread/shellCommand` as a
    local-host escape hatch, so it must use an available local environment
    even when a remote environment is primary.
    
    ## What changed
    
    - Add `ResolvedTurnEnvironments::local()` to find the selected local
    environment.
    - Resolve the user shell command cwd and shell from that local
    `TurnEnvironment`.
    - Emit the standard `shell is unavailable in this session` error when no
    selected local environment or resolved local shell is available.
    - Add an integration test covering `/shell` without a local environment.
    
    ## Test plan
    
    - `just test -p codex-core
    user_shell_command_without_local_environment_emits_error`
  • [codex] Use expect in integration tests (#28441)
    The workspace denies `clippy::expect_used` in production. Although
    `clippy.toml` allows `expect` in tests, Bazel Clippy compiles
    integration-test helper code in a way that does not receive that
    exemption, which encouraged verbose `unwrap_or_else(... panic!(...))`
    and equivalent `match`/`let else` forms.
    
    This allows `clippy::expect_used` once at each integration-test crate
    root (including aggregated suites and test-support libraries), then
    replaces manual panic-based Result and Option unwraps with
    `expect`/`expect_err`. Standalone `tests/*.rs` files remain their own
    crate roots. Intentional assertion and unexpected-variant panics remain
    unchanged, and the production `expect_used = "deny"` lint remains in
    place.
    
    The cleanup is mechanical and net-negative in line count.
  • [codex] Add interruptible sleep tool (#28429)
    ## Why
    
    Models sometimes need to pause briefly while waiting for external work,
    but using a shell command for that delay ties the wait to a process and
    does not naturally resume when new turn input arrives.
    
    ## What changed
    
    - add a built-in `sleep` tool behind the under-development `sleep_tool`
    feature
    - accept a bounded `duration_ms` argument, matching the millisecond
    convention used by unified exec
    - end the sleep early when either steered user input or mailbox input
    arrives
    - include elapsed wall-clock time in completed and interrupted outputs
    - emit a dedicated core `SleepItem` through `item/started` and
    `item/completed`
    - expose the sleep item as app-server v2 `ThreadItem::Sleep` and retain
    it in reconstructed thread history
    - regenerate the configuration schema for the new feature flag
    - regenerate app-server JSON and TypeScript schema fixtures
    
    ## Test plan
    
    - `just test -p codex-core sleep_tool_follows_feature_gate`
    - `just test -p codex-core any_new_input_interrupts_sleep`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    sleep_emits_started_and_completed_items`
  • [codex] Bind shell snapshots to retained thread environments (#28421)
    ## Why
    
    Shell snapshots are currently session-scoped even though shell and cwd
    are properties of a selected turn environment. That makes snapshot
    refresh depend on separate session-cwd plumbing, prevents retained
    environments from retaining their snapshot work, and can make snapshot
    construction use a different shell than command execution.
    
    This follows #27955 by making the retained thread-environment service
    own environment snapshot lifecycles. Session configuration remains the
    requested selection state, while `ThreadEnvironments` remains the source
    of successfully resolved environments.
    
    ## What changed
    
    - Configure the shell-snapshot builder before initial environment
    resolution.
    - Start each local environment snapshot task when its `TurnEnvironment`
    is built and retain that shared task while environment ID and cwd still
    match.
    - Inherit retained environment snapshots into spawned child threads.
    - Carry the selected `TurnEnvironment` through shell runtimes so
    snapshot construction and command execution use the same
    environment-specific shell and cwd.
    - Load project instructions and warm plugins/skills after initial
    environment resolution.
    - Continue decoding invalid UTF-8 instruction files lossily without
    emitting a startup warning.
    - Keep requested selections in `SessionConfiguration`; failed or
    duplicate resolutions only affect the resolved environment snapshot.
    
    ## Validation
    
    - `cargo check -p codex-core --tests`
    - `just test -p codex-home instructions` (6 passed)
    - Focused environment, instruction, shell-snapshot, and user-shell tests
    (84 passed)
    - Focused shell-snapshot, user-shell, and unified-exec tests (126
    passed; two event-timing tests passed on retry)
  • Run core integration tests against a Wine-backed Windows executor (#28401)
    ## Why
    
    We want to exercise a linux app-server against a windows exec-server
    without having to repeat every test case. This approach has slight
    precedent in the remote docker test setup.
    
    ## What
    
    Run the shared `codex-core` integration suite against Windows
    exec-server behavior from Linux. This makes cross-OS path and shell
    regressions visible while keeping unsupported cases owned by individual
    tests.
    
    - Add `local`, `docker`, and `wine-exec` test environment selection with
    legacy Docker compatibility.
    - Extend `codex_rust_crate` to generate a sharded Wine-exec variant
    using a cross-built Windows server and pinned Bazel Wine/PowerShell
    runtimes.
    - Teach remote-aware helpers about Windows paths and track temporary
    incompatibilities with source-local `skip_if_wine_exec!` calls and
    follow-up reasons.
  • Add a toggle for realtime startup context (#28405)
    ## Summary
    - Add `includeStartupContext` to realtime start requests so callers can
    explicitly skip Codex startup context while keeping the backend prompt
    - Thread the new flag through protocol types, request processing, and
    realtime session config
    - Update app-server docs and coverage for the new default and opt-out
    behavior
    
    ## Testing
    - Added protocol serialization coverage for `includeStartupContext`
    - Added realtime integration coverage for starting a session with
    startup context disabled
  • Add realtime speech append control (#27917)
    ## Why
    
    Realtime voice harness tuning needs app-side control over what backend
    Codex text is spoken. Backend orchestrator text is written for a reading
    UI, so automatically speaking every preamble, progress update, or final
    assistant message can make the realtime voice model too chatty.
    
    For experimentation, clients need two simple controls: keep app/client
    text-item injection on the existing item-create path, and add an
    explicit speakable path that app code can call only when it wants
    realtime to speak. Automatic Codex output also needs an opt-in way to
    switch from the protocol's default speakable path to regular realtime
    items, with a caller-provided prefix so prompt wording can be tuned
    outside core.
    
    The default remains unchanged: if a client omits the new start fields
    and never calls `appendSpeech`, automatic backend output continues down
    the existing speakable path for the selected realtime protocol.
    
    ## What Changed
    
    - Adds experimental `thread/realtime/appendSpeech` for app-provided
    speakable text.
    - Keeps existing `thread/realtime/appendText` as the item-create API for
    app-provided realtime text items.
    - Adds `codexResponsesAsItems` / `codex_responses_as_items` on
    `thread/realtime/start` to send automatic Codex responses with
    `conversation.item.create` instead of the protocol's default speakable
    output path.
    - Adds `codexResponseItemPrefix` / `codex_response_item_prefix` so
    clients can prepend experiment instructions to those automatic Codex
    response items.
    - Keeps literal `conversation.handoff.append` routing scoped to the v1
    speakable path; v2 default speech uses its item/function-output plus
    `response.create` behavior.
    - Removes the earlier public silent-context API and hardcoded
    silent-context prefix.
    - Updates realtime tests to cover default automatic speakable behavior,
    opt-in automatic item-create behavior, and explicit `appendSpeech`
    behavior.
    
    ## Validation
    
    - `cargo check -p codex-core -p codex-app-server -p codex-api`
    - `just test -p codex-app-server realtime_conversation`
    - `just test -p codex-core realtime_conversation` (50/51 passed in the
    filtered parallel run; the lone failure passed when rerun in isolation)
    - `just test -p codex-core
    conversation_mirrors_assistant_message_text_to_realtime_handoff`
    - `just test -p codex-api
    e2e_connect_and_exchange_events_against_mock_ws_server`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
    - `cargo build -p codex-cli`
  • Deflake realtime handoff steering test (#28300)
    ## Summary
    - keep the realtime mock websocket open for the handoff steering test
    after scripted responses
    - avoid racing the mock server close before the standalone handoff
    append is observed, which was showing up as a Windows timeout in CI
    
    __Details__:
    Failures in samples seem to be caused by:
    1. The mock websocket sends conversation.handoff.requested.
    2. The mock immediately closes the websocket because
    start_websocket_server(...) defaults to close_after_requests: true.
    3. On Windows, that close often surfaces as os error 10053 / 10054.
    4. The realtime stream shuts down before the routed handoff finishes
    creating/steering the follow-up request.
    5. The test waits for the expected follow-up event and times out.
    
    The PR changes only step 2: for this test, the mock websocket stays open
    after sending the scripted handoff event. The same handoff event is
    still sent, and the test still asserts the important steering behavior:
    1. first Responses request has the original prompt
    2. first request does not contain realtime delegation
    3. second Responses request does contain the realtime delegation
    
    ## Validation
    - `just fmt`
    - `just test -p codex-core --test all
    suite::realtime_conversation::inbound_handoff_request_steers_active_turn`
    
    ## Recent CI failures with the same signature
    
    -
    https://github.com/openai/codex/actions/runs/27538033492/job/81392362858
      - 2026-06-15, `[codex] update multi-agent v2 prompts`
    - same test failed after `conversation.handoff.requested`; websocket
    read failed with `os error 10053`
    
    -
    https://github.com/openai/codex/actions/runs/27543877820/job/81412200651
    - 2026-06-15, `feat: dispatch queued user messages through core idle
    extensions`
      - same test failed; websocket read failed with `os error 10054`
    
    -
    https://github.com/openai/codex/actions/runs/27544342375/job/81413801641
      - 2026-06-15, `[codex] Make marketplace loading capability aware`
      - same test failed; websocket read failed with `os error 10053`
  • Respect blocking PostToolUse hooks in code mode (#28365)
    ## Summary
    
    Make blocking hook behavior reliable for tools invoked from code mode.
    
    Previously, a `PostToolUse` hook could block a completed tool result,
    but code mode would still return the original typed result to
    JavaScript. The hook appeared blocked in hook telemetry while the
    running script continued with the result.
    
    This change:
    
    - rejects the nested JavaScript tool promise when `PostToolUse` blocks
    - normalizes `decision: "block"` and exit code 2 to the same blocking
    behavior
    - surfaces the hook feedback as the rejected promise's error
    - adds end-to-end coverage for the relevant PreToolUse and PostToolUse
    interactions
    
    ## Hook semantics in code mode
    
    | Hook behavior | Code-mode result |
    |---|---|
    | PreToolUse block | Reject the promise before the tool executes |
    | PreToolUse `updatedInput` | Execute the rewritten invocation and
    return its result |
    | PostToolUse `decision: "block"` | Execute the tool, then reject the
    promise with the hook reason |
    | PostToolUse exit code 2 | Same behavior as `decision: "block"` |
    | PostToolUse `continue: false` | Preserve the existing feedback-only
    behavior; do not reject the promise |
    
    ## Test coverage
    
    Added or strengthened end-to-end coverage proving that:
    
    - a PreToolUse block rejects the JavaScript promise before execution
    - a PreToolUse input rewrite executes only the rewritten command
    - JavaScript receives the rewritten command's result
    - PostToolUse `decision: "block"` rejects after the command executes
    - PostToolUse exit code 2 has the same behavior
    - the hook observes the original completed tool response
    - the blocked original result does not reach JavaScript
    - existing direct-mode replacement behavior remains intact
    - `continue: false` without a reason produces deterministic fallback
    feedback
  • feat(core): add metadata field to ResponseItem (#28355)
    ## Description
    
    This PR adds an optional `metadata` field to `ResponseItem` for
    Responses API calls. Only mechanical plumbing, no actual values
    populated and sent yet. Turns out just adding a new field to
    `ResponseItem` has quite a large blast radius already.
    
    This change is backwards compatible because `metadata` is optional and
    omitted when absent, so existing response items and rollout history
    without it still deserialize and requests that do not set it keep the
    same wire shape. For provider compatibility, we strip out `metadata`
    before non-OpenAI Responses requests so Azure and AWS Bedrock never see
    this field.
    
    My followup PR here will actually make use of it to start storing and
    passing along `turn_id`: https://github.com/openai/codex/pull/28360
    
    ## What changed
    
    - Added `ResponseItemMetadata` with optional `turn_id`, plus optional
    `metadata` on Responses API item variants and inter-agent communication.
    - Preserved item metadata through response-item rewrites such as
    truncation, missing tool-output synthesis, compaction history
    rebuilding, visible-history conversion, rollout/resume, and generated
    app-server schemas/types.
    - Strip item metadata from non-OpenAI Responses requests while
    preserving it for OpenAI-shaped requests.
    - Updated the mechanical fixture/test construction churn required by the
    new optional field.
  • core: let steer interrupt wait_agent (#28341)
    ## Why
    
    `wait_agent` can block for a long timeout while waiting for sub-agent
    mailbox activity. Although same-turn user steer is accepted during that
    tool call, the input remains pending until the wait returns, so an
    explicit request to change direction can appear unresponsive.
    
    ## What changed
    
    - Notify active `wait_agent` calls when user input is steered into the
    current turn.
    - Check for already-pending steer input when subscribing so input that
    races with tool startup is not missed.
    - Distinguish mailbox activity, steered input, and timeout outcomes,
    returning `Wait interrupted by new input.` for the steer path.
    - Update the `wait_agent` tool description to document the early-return
    behavior.
    
    ## Testing
    
    - `just test -p codex-core input_queue_`
    - `just test -p codex-core wait_agent`
    
    The coverage includes steer notification before and after subscription,
    plus an end-to-end test that verifies the interrupted wait result and
    steered user input are both included exactly once in the follow-up model
    request.
  • Represent dynamic tools with explicit namespaces internally (#27365)
    Follow-up to #27356.
    
    ## Stack note
    
    This PR changes Codex's internal dynamic-tool shape while leaving
    `thread/start` unchanged. App-server therefore converts the existing
    per-tool input into explicit functions and namespaces before passing it
    to core.
    
    [#27371](https://github.com/openai/codex/pull/27371) updates
    `thread/start` to use the same explicit shape and removes this temporary
    conversion.
    
    ## Why
    
    Dynamic tools repeat namespace metadata on every function. Core should
    keep one explicit namespace with its member tools so descriptions and
    membership stay consistent across sessions and runtime planning.
    
    ## What changed
    
    - Represent dynamic tools as top-level functions or explicit namespaces
    in protocol and session state.
    - Read old flat rollout metadata and write the canonical hierarchy.
    - Flatten namespace members only when registering callable tools.
    - Keep `thread/start.dynamicTools` flat for now and normalize it at the
    app-server boundary.
    
    New builds can read old rollout metadata. Older builds cannot read newly
    written hierarchical metadata.
    
    ## Test plan
    
    - `just test -p codex-app-server
    thread_start_normalizes_legacy_dynamic_tools_into_model_request`
    - `just test -p codex-protocol
    session_meta_normalizes_legacy_dynamic_tools`
    - `just test -p codex-core
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
    - `just test -p codex-core
    tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call`
    - `just test -p codex-core code_mode_can_call_hidden_dynamic_tools`
    - `just test -p codex-tools`
  • [codex] exec-server honors remote environment cwd and shell (#28122)
    ## Why
    
    Next slice needed to make progress on the `remote_env_windows` test is
    to support passing a Windows cwd for the remote environment and using
    that environment's native shell. This lets the test run a real Windows
    process instead of only recording an early path or shell mismatch.
    
    ## What
    
    - change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
    `PathUri`
    - convert local cwd values to URIs when constructing selections
    - preserve a remote primary cwd instead of replacing it with the local
    legacy fallback
    - prefer the selected environment's discovered shell for unified exec,
    falling back to the session shell when unavailable
    - convert back to a host-native absolute path at current native-only
    consumer boundaries
    - reject or deny unsupported foreign cwd values at the existing
    request-permissions boundary, with TODOs for its future migration
    - extend the hermetic Wine test to execute Windows PowerShell in
    `C:\windows` and verify successful process completion
    - record the current app-server rejection against the same Wine-backed
    remote Windows fixture when its cwd is supplied as a native Windows path
  • [codex] Dedupe plugin MCPs by app declaration name (#27607)
    ## Context
    
    This is the next step in the plugin auth-routing stack. The earlier PRs
    make `PluginsManager` auth-aware and move the broad App/MCP surface
    decision into that layer. This PR narrows the ChatGPT/SIWC behavior so
    we only hide a plugin MCP server when it conflicts with an App
    declaration of the same name.
    
    In product terms: if a plugin exposes both an App route and MCP route
    for `foo`, ChatGPT/SIWC sessions should use the App route for `foo`. If
    the same plugin also exposes a separate MCP server like `foo2`, that MCP
    server should remain available.
    
    ```json
    // .app.json
    {
      "apps": {
        "foo": {
          "id": "connector_abc"
        }
      }
    }
    ```
    
    ```json
    // .mcp.json
    {
      "mcpServers": {
        "foo": {
          "url": "https://mcp.foo.com/mcp"
        },
        "foo2": {
          "url": "https://mcp.foo2.com/mcp"
        }
      }
    }
    ```
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Preserve App declaration names in loaded plugin metadata.
    - Keep public effective App outputs as deduped connector IDs for
    existing callers.
    - For ChatGPT/SIWC, suppress only plugin MCP servers whose names match
    declared App names.
    
    ## Validation
    
    ```bash
    cargo fmt --all
    cargo test -p codex-core-plugins plugin_auth_projection
    cargo test -p codex-core-plugins effective_apps
    cargo test -p codex-core-plugins read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning
    cargo test -p codex-core explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins
    cargo test -p codex-core explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth
    cargo test -p codex-app-server --test all plugin_install_filters_disallowed_apps_needing_auth
    git diff --check
    ```
    
    ---------
    
    Co-authored-by: Xin Lin <xl@openai.com>
  • [codex] Carry exec-server cwd as PathUri (#28032)
    ## Why
    
    This is the second-to-last place in the exec-server protocol that needs
    to migrate to URIs to support cross-OS operation.
    
    ## What
    
    - Change `ExecParams.cwd` to `PathUri`.
    - Keep the cwd URI-shaped through core and rmcp producers, converting it
    to `AbsolutePathBuf` only in `LocalProcess::start_process`.
    - Reject non-native cwd URIs before launch and update the affected
    protocol documentation and call sites.
  • [codex] Send turn state through compact requests (#28002)
    ## Context
    
    Inline compaction is part of the active logical turn. Compact requests
    and the sampling requests around them should use the same turn state,
    including when compaction is the first request to establish it.
    
    ## Change
    
    Pass the turn-scoped `OnceLock` directly to inline v1 compaction so
    `/responses/compact` includes an established value in the existing HTTP
    header. Capture `x-codex-turn-state` from the compact response into that
    same lock, allowing pre-turn compact to establish the value that
    subsequent sampling reuses.
    
    V2 compact already uses the normal Responses HTTP/WebSocket path and
    continues to share the same `OnceLock` without separate plumbing. The
    first returned value wins for the logical turn.
    
    ## Test plan
    
    Integration coverage verifies that:
    
    - pre-turn v1 compact can establish state for the first sampling request
    - inline v1 compact receives established state over HTTP
    - inline v2 compact reuses established state over HTTP
    - inline v2 compact reuses established state over WebSocket
    
    CI validates the full change.
  • [codex] Send request-scoped turn state over WebSocket (#27996)
    ## Context
    
    Turn state is scoped to one logical turn, but the WebSocket path
    currently exchanges it through upgrade headers, which are scoped to the
    physical connection. A connection may be reused across turns, so its
    handshake cannot represent the turn lifecycle reliably.
    
    ## Change
    
    Exchange turn state on each WebSocket response request instead:
    
    - send an established value in `response.create.client_metadata`
    - read the returned value from the existing `response.metadata` event
    - retain the first value in the turn-scoped `ModelClientSession`
    `OnceLock`
    - start the next logical turn without state, even when it reuses the
    same WebSocket connection
    
    This gives WebSocket requests the same first-value-wins contract as the
    existing HTTP path.
    
    ## Test plan
    
    Integration coverage verifies that:
    
    - WebSocket replays returned state on same-turn follow-ups
    - later response metadata does not replace the first value
    - state resets at the logical turn boundary without requiring a
    reconnect
    
    CI validates the full change.
    
    ## Stack
    
    This is 1/2. #28002 builds on this request-scoped transport to carry
    established state through compact requests.
  • [codex] Add hermetic Wine exec-server test (#27937)
    ## Why
    
    We want to make it possible for an app-server orchestrator on one OS to
    control an exec-server on another host running a different OS. In
    practice this kinda already works if you get lucky and the two hosts
    have the same path format, but we mangle quite a lot of operations if
    either end is Windows.
    
    This test starts exercising that interaction, although right now the
    initial bootstrap fails. Future changes will expand the test's
    assertions to match improved support.
    
    ## What
    
    Stacked on #27964. This adds a small Windows exec-server fixture and a
    Linux protocol smoke test using the reusable Wine harness, covering
    Windows environment discovery, non-TTY `cmd.exe` execution, output, exit
    status, and working directory.
    
    Once we've got the full codex binary cross-building under Bazel we could
    consider moving to the real binary instead of the stripped down
    exec-server-only binary used here.
  • [codex] Gate plugin MCP servers by auth route (#27459)
    ## Context
    
    Some plugins expose both Apps and MCP servers. This PR moves auth-aware
    surface projection into `core-plugins::PluginsManager`, so callers get a
    consistent effective plugin view. Later PRs narrow the conflict rule and
    update listing/install paths.
    
    The high level goal of this PR is to set up the plumbing to
    conditionally filter App/MCP in the plugin manager layer. We start by
    removing MCP servers when using SIWC/Codex-backend auth, and removing
    Apps when using API-key-style auth.
    
    This PR is now stacked on #27652, which contains only the constructor
    plumbing for seeding `PluginsManager` with the current auth mode.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - API-key/non-ChatGPT routes hide plugin Apps and keep plugin MCPs.
    - ChatGPT/SIWC with Apps enabled keeps plugin Apps and suppresses MCPs
    for dual-surface plugins.
    - MCP-only plugins stay available for ChatGPT/SIWC sessions.
    - Cached plugin load outcomes are re-projected when auth mode changes.
    
    ## Validation
    
    ```bash
    cargo test -p codex-core-plugins plugin_auth_projection
    cargo test -p codex-core list_tool_suggest_discoverable_plugins
    git diff --check
    ```
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • [codex] Let generic test turns inherit their environment (#27972)
    ## Why
    
    The paired thread-environment migration changed several generic test
    turn helpers from supplying a fallback cwd to explicitly selecting the
    local environment. That changes their meaning under
    `build_with_remote_env()`: remote-only fixtures cannot resolve the
    forced local selection, so the tests fail before exercising apply-patch,
    RMCP, unified-exec, or view-image behavior.
    
    Generic helpers should inherit the environment selected by their
    fixture. Tests that intentionally exercise local routing continue to
    select the local environment explicitly.
    
    ## What changed
    
    - Remove forced `local_selections(...)` overrides from the generic
    apply-patch, RMCP, unified-exec, and view-image turn helpers.
    - Remove the imports made unused by those deletions.
    
    ## Testing
    
    - Not run locally; this is a test-fixture-only change and the `full-ci`
    branch will exercise the affected remote shards.
  • [codex] add roles to realtime append text (#27936)
    ## Summary
    
    Add an explicit `user` or `developer` role to
    `thread/realtime/appendText` and propagate it through the realtime input
    queue into `conversation.item.create`. Older JSON clients that omit the
    field continue to default to `user`.
    
    This lets app-provided context such as memory retain developer authority
    without bypassing app-server through a renderer-owned data channel. The
    app-server schemas, API documentation, and focused protocol and
    websocket coverage are updated with the new contract.
    
    The Codex Apps consumer is tracked in
    [openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • Support plaintext agent messages (#27830)
    ## Why
    
    Multi-agent v2 `send_message` deliveries already reach the receiving
    model as typed `agent_message` items with encrypted content.
    Child-completion notifications are generated by Codex itself, so their
    content is plaintext and previously fell back to a serialized JSON
    envelope inside an assistant message.
    
    With plaintext `input_text` supported for `agent_message`, both delivery
    paths can use the same model-visible type while preserving explicit
    author and recipient metadata.
    
    ## What changed
    
    - add plaintext `input_text` support to `AgentMessageInputContent` and
    regenerate the affected app-server schemas
    - preserve `InterAgentCommunication` as structured mailbox input instead
    of converting it to assistant text
    - record delivered communications as typed `agent_message` history items
    - persist a dedicated rollout item so local delivery metadata such as
    `trigger_turn` remains available without leaking into the Responses
    request
    - reconstruct typed agent messages on resume and preserve fork-turn
    truncation behavior
    - remove request-time assistant-content parsing
    - preserve plaintext and encrypted inter-agent deliveries in stage-one
    memory inputs
    - normalize and link plaintext and encrypted agent messages in rollout
    traces without treating inbound messages as child results
    - cover the real MultiAgent V2 child-completion path end to end with
    deterministic mailbox synchronization
    
    ## Verification
    
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core input_queue_drains_mailbox_in_delivery_order
    record_initial_history_reconstructs_typed_inter_agent_message
    fork_turn_positions_use_inter_agent_delivery_metadata`
    - `just test -p codex-memories-write
    serializes_inter_agent_communications_for_memory`
    - `just test -p codex-rollout-trace
    agent_messages_preserve_routing_and_content
    sub_agent_started_activity_creates_spawn_edge`
    - `just test -p codex-rollout-trace
    agent_result_edge_falls_back_to_child_thread_without_result_message`
    - `just test -p codex-protocol -p codex-rollout -p
    codex-app-server-protocol`
  • fix(plugins) rm plugin descriptions (#23254)
    ## Summary
    Removes Plugin descriptions from the dev message, since descriptions of
    skills and MCPs cover the capabilities offered by the plugin.
    
    ## Testing
    - [x] Updates unit tests
  • chore: prompt MAv2 (#27919)
    Prompt update of MAv2
  • realtime: add AVAS architecture override (#27720)
    ## Summary
    
    Adds a `RealtimeConversationArchitecture` option for realtime
    conversation startup, with `realtimeapi` as the default and `avas` as an
    opt-in architecture.
    
    The AVAS path is limited to realtime v1 conversational WebRTC starts,
    and WebRTC call creation appends `intent=quicksilver&architecture=avas`
    to `/v1/realtime/calls`. The existing sideband websocket still joins by
    `call_id`.
    
    This also exposes the per-session architecture override through
    app-server v2 `thread/realtime/start` params and updates the config
    schema for `[realtime].architecture`.
    
    ## Validation
    
    - `just fmt`
    - `just write-config-schema`
    - `just test -p codex-api sends_avas_session_call_query_params`
    - `just test -p codex-core -E
    'test(~conversation_webrtc_start_uses_avas_architecture_query)'`
    - `just test -p codex-core -E 'test(realtime_loads_from_config_toml)'`
    - `just test -p codex-app-server-protocol -E
    'test(~serialize_thread_realtime_start) |
    test(generated_ts_optional_nullable_fields_only_in_params)'`
    - `just test -p codex-app-server -E
    'test(realtime_webrtc_start_emits_sdp_notification)'`
  • [ez][codex-rs] Support approvals reviewer in app defaults (#27075)
    [from codex]
    
    ## Summary
    
    - add `approvals_reviewer` support to `[apps._default]`
    - resolve connected-app reviewers in per-app, app-default, then global
    order
    - expose the setting through the v2 config API and regenerate schema
    fixtures
    
    ## Context
    
    PR #25167 added `apps.<connector_id>.approvals_reviewer`, but the shared
    app defaults table could not specify the reviewer. This extends the same
    behavior to `[apps._default]` while preserving per-app overrides.
    
    Managed `allowed_approvals_reviewers` requirements still constrain both
    default and per-app values. A disallowed app value falls back to the
    global reviewer, and non-app MCP servers continue using the global
    reviewer.
    
    ## Testing
    
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just fmt`
    - `just test -p codex-config`
    - `just test -p codex-core app_approvals_reviewer`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server config_read_includes_apps`
  • [code-mode] Reject remote image URLs from output helpers (#27732)
    ## Summary
    
    - reject HTTP(S) image URLs from the shared code-mode output-image
    normalization path
    - return a concise model-visible tool error so the model can recover on
    its next turn
    - apply the targeted rejection to both `image()` and `generatedImage()`
    - leave other non-empty image URL values to existing downstream handling
    
    The returned error is:
    
    > Tool call failed: remote image URLs are not supported in tool outputs.
    Pass a base64 data URI instead
    
    ## Why
    
    Responses Lite cannot lower a remote image URL emitted from a structured
    tool output. Rejecting HTTP(S) values in the Codex harness preserves the
    tool-call metadata and gives the model a recoverable next turn instead
    of invalidating the sample.
    
    ## Test coverage
    
    The regression is covered primarily by a `test_codex()` agent
    integration test that simulates the Responses API exchange and asserts
    the failed model-visible exec output. A supplemental runtime test covers
    both `http://` and `https://` inputs across both image output helpers.
    
    ## Test plan
    
    - `cd codex-rs && just test -p codex-code-mode`
    - `cd codex-rs && just test -p codex-code-mode-protocol`
    - `cd codex-rs && just test -p codex-core
    code_mode_image_helper_rejects_remote_url`
    - `cd codex-rs && just fmt`
    - `git diff --check origin/main...HEAD`
    
    Related context: https://github.com/openai/openai/pull/1022346