Stacked on #27365.
## Stack note
[#27365](https://github.com/openai/codex/pull/27365) kept `thread/start`
unchanged and converted its input in `thread_processor`. This PR updates
`thread/start` to accept explicit functions and namespaces directly.
Legacy per-tool arrays are still accepted and converted while reading
the request. As a result, `thread_processor` can validate and pass the
tools through directly, which is why some code added in #27365 is
removed here.
## Why
`thread/start.dynamicTools` still repeats namespace data on each
function even though core now stores explicit namespace groups. The
request API should use the same shape so each namespace has one
description and one member list.
## What changed
- Accept top-level functions and explicit namespace objects in
`dynamicTools`.
- Continue accepting fully legacy flat arrays, including
`exposeToContext`.
- Reject arrays that mix legacy and canonical entries.
- Reuse the protocol types directly and remove the temporary app-server
adapter.
- Update validation, docs, the test client, and generated schemas.
## Test plan
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server
dynamic_tool_call_round_trip_sends_text_content_items_to_model`
- `just test -p codex-app-server
thread_start_normalizes_legacy_dynamic_tools_into_model_request`
- `just test -p codex-app-server
thread_start_rejects_mixed_dynamic_tool_formats`
- `just test -p codex-app-server
thread_start_rejects_hidden_dynamic_tools_without_namespace`
## Why
Memory read telemetry currently reconstructs the executable shell
command after a tool call finishes. That duplicates shell, login-policy,
and cwd resolution owned by the tool handlers, and can diverge from the
environment-specific command that unified exec actually ran.
## What changed
- Expose the existing restricted shell-script parser directly for raw
script text.
- Parse `shell_command` and `exec_command` input into plain command argv
before classifying memory reads.
- Preserve all-or-nothing safe-command validation for multi-command
scripts.
- Remove cwd resolution, shell selection, and the unnecessary async
boundary from memory read metric emission.
## Testing
- `just test -p codex-shell-command`
- `cargo check -p codex-core`
## Why
The ws pump refactor removed the relay keepalive timers that had been
added to keep idle rendezvous connections alive. An idle relay could
therefore be closed by the rendezvous service or a load balancer,
disconnecting executor-backed MCP processes.
## What
- restore periodic WebSocket ping frames on both rendezvous relay
endpoints
- keep missed-tick behavior bounded with `MissedTickBehavior::Skip`
- cover the harness and remote-environment pumps with focused
traffic-after-keepalive tests
## Why
`terminal_resize_reflow` is now stable and should behave as always on.
Keeping the disabled runtime paths around made the feature look
configurable even though the rollout is complete, and old config could
still suggest there was a supported off mode.
## What Changed
- Marked `terminal_resize_reflow` as `Stage::Removed` while keeping it
default-enabled for compatibility.
- Ignored `[features].terminal_resize_reflow` config entries so stale
`false` settings no longer affect the effective feature set.
- Removed TUI branches that depended on the flag being disabled, so
draw, replay buffering, stream finalization, and resize scheduling all
assume resize reflow is active.
- Simplified resize smoke coverage to exercise the always-on behavior
only.
## Verification
- `just test -p codex-features`
- `just test -p codex-tui resize_reflow`
- `just test -p codex-tui initial_replay_buffer
thread_switch_replay_buffer`
## Why
Shell snapshot lifecycle state was split between `Shell` and
`SessionServices`: `Shell` carried the receiver while session code
exposed and forwarded the raw sender. That coupled shell identity to
mutable snapshot state and made refresh, inheritance, and file lifetime
harder to reason about.
## What changed
- make each `Arc<ShellSnapshot>` represent one cwd-specific snapshot
generation
- store the active generation in `SessionServices` with `ArcSwapOption`
- have construction start the background build and expose only a
cwd-validated snapshot path
- use `ShellSnapshotFile` ownership to delete snapshot files
automatically
- pass snapshot paths explicitly to shell runtimes instead of storing
snapshot state on `Shell`
- preserve inherited and in-flight generations by pinning their `Arc`
while they are in use
## Test plan
- `cargo check -p codex-core --lib`
- `just test -p codex-core 'shell_snapshot::tests'`
- `just test -p codex-core
shell_command_snapshot_still_intercepts_apply_patch`
- `just test -p codex-core
shell_snapshot_deleted_after_shutdown_with_skills`
## Why
App-server threads without a local executor need orchestrator-owned
skills from the hosted `codex_apps` MCP server. Threads with the local
executor already discover installed skills from the local filesystem.
After the orchestrator skill provider was enabled for every app-server
thread, local-executor threads also received the hosted skill catalog
and the `skills.list` and `skills.read` tools. This changed the existing
local behavior and could expose a second hosted copy of a skill that was
already installed locally.
## What changed
- Expose the thread's selected execution environments to extensions at
thread startup.
- Enable orchestrator skills only when the reserved local environment is
not selected.
- Apply that decision consistently to hosted skill catalog discovery,
explicit skill injection, and the `skills.list` and `skills.read` tools.
## Verification
- The existing no-executor app-server test continues to verify hosted
skill discovery, invocation, and child-resource reads.
- A new app-server test verifies that local-executor threads do not
receive hosted skill context or `skills.*` tools.
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`
## Summary
- cap feedback log uploads to at most eight threads before SQLite log
aggregation and rollout attachment resolution
- keep the root session included while bounding descendant fanout during
`/feedback` uploads
## Why
Very large sessions can accumulate large spawned-thread subtrees.
Feedback uploads currently walk the entire subtree and then read each
resolved rollout into memory, which can blow up when one session has
hundreds of descendants.
## Validation
- ran `just fmt`
- did not run tests or Clippy per request; CI will cover validation
## Why
#27870 teaches the MCP extension how to discover stdio MCP servers
declared by a selected executor plugin, but app-server does not yet
install that contributor or initialize its per-thread state. As a
result, `thread/start.selectedCapabilityRoots` can select the plugin
while its MCP servers remain inactive.
This PR closes that app-server wiring gap:
```text
thread/start(selectedCapabilityRoots)
-> initialize the thread's selected-plugin MCP snapshot
-> read the selected plugin's .mcp.json through its environment
-> start declared stdio servers in that environment
-> expose their tools only on the selected thread
```
## What changed
- Install the selected-executor-plugin MCP contributor in app-server
using the existing shared `EnvironmentManager`.
- Initialize its frozen thread snapshot when `thread/start` includes
selected capability roots.
- Document that selected plugin stdio MCPs are activated in their owning
environment.
- Add an app-server E2E covering the complete selection-to-tool-call
path.
The E2E verifies that:
- the selected MCP process receives an executor-only environment value,
proving the tool runs through the selected environment;
- the MCP tool is advertised to the model and can be called;
- a normal MCP config reload does not discard the thread's frozen
selected-plugin registration;
- another thread without the selected root does not see the MCP server.
## Scope
- Existing sessions without `selectedCapabilityRoots` are unchanged.
- Only stdio MCP declarations are activated. HTTP declarations remain
inactive.
- This does not change selected-root persistence across resume/fork or
add hosted-plugin behavior.
## Verification
- Focused app-server E2E:
`selected_executor_plugin_exposes_its_stdio_mcp_only_to_that_thread`
## Stack
Stacked on #27870.
## Context
This is PR5 in the plugin auth-routing stack. Earlier PRs make plugin
surface projection auth-aware, narrow App/MCP conflicts by App
declaration name, and keep connector listings auth-aware. This PR
applies the same name-based App/MCP conflict rule into plugin MCP
loading, so install-time MCP OAuth and plugin detail metadata both
reflect the MCPs available for the current auth route.
## Stack
- PR1: #27652 seed plugin manager auth at construction.
- PR2: #27459 route plugin surfaces by auth mode.
- PR3: #27607 dedupe plugin MCP servers by App declaration name.
- PR4: #27602 preserve plugin Apps in connector listings.
- PR5: #27461 skip install-time plugin MCP OAuth for matching App
routes.
## Summary
- Make `load_plugin_mcp_servers` auth-aware and let it load App
declarations before filtering same-name MCP servers for Codex-backend
auth.
- Use that filtered MCP list for both install-time MCP OAuth and
marketplace plugin detail metadata.
- Preserve API-key/direct auth behavior so plugin MCP servers remain
visible and can still start OAuth.
## Validation
```bash
cargo fmt --all
cargo test -p codex-core-plugins read_plugin_for_config_filters_mcp_servers_for_codex_backend_auth
cargo check -p codex-core-plugins -p codex-app-server
git diff --check
git diff --cached --check
```
## Context
This is PR4 in the plugin auth-routing stack. The earlier PRs make
plugin surface projection auth-aware and narrow App/MCP conflicts by App
declaration name. This PR keeps connector listing paths aligned with
that projected plugin App set.
This means ChatGPT/SIWC users will still see plugin-provided Apps in
connector listing surfaces like the Apps/connector picker, while API-key
users will not see Apps they cannot use.
## Stack
- PR1: #27652 seed plugin manager auth at construction.
- PR2: #27459 route plugin surfaces by auth mode.
- PR3: #27607 dedupe plugin MCP servers by App declaration name.
- PR4: #27602 preserve plugin Apps in connector listings.
- PR5: #27461 skip install-time plugin MCP OAuth for matching App
routes.
## Summary
- Have app-server compute effective plugin Apps from the existing
PluginsManager and pass them into connector listing.
- Keep plugin Apps visible in Apps/connector listing for ChatGPT/SIWC
users.
- Keep API-key-style auth from surfacing plugin Apps in connector
listings.
## Validation
```bash
cargo test -p codex-chatgpt connectors::tests
cargo test -p codex-app-server list_apps_includes_plugin_apps_for_chatgpt_auth
git diff --check
```
## Summary
- align the default multi-agent v2 root and subagent hints with the
evaluated prompt guidance for direct collaboration-tool calls, parallel
delegation, and shared workspaces
- keep the current `interrupt_agent` tool name and existing
concurrency-hint placement, with the explicit no-spawn instruction last
- document the context tradeoff between `fork_turns="none"` and
`fork_turns="all"` in the v2 `spawn_agent` description
- extend the focused prompt and tool-surface tests
## Why
The evaluated multi-agent prompt includes operational guidance that is
missing from the current Codex defaults. This applies that guidance to
the current tool surface without restoring stale `close_agent` or
duplicated concurrency wording.
## User impact
Multi-agent v2 receives clearer instructions about when and how to
parallelize work, how agent workspaces interact, and how `fork_turns`
affects subagent context. The existing default opt-out behavior remains
in place.
## Testing
- `just fmt`
- `just test -p codex-core
multi_agent_v2_default_usage_hints_use_configured_thread_cap`
- `just test -p codex-core
multi_agent_feature_selects_one_agent_tool_family`
## Why
**In short:** this PR discovers MCP registrations by reading a selected
plugin's `.mcp.json` on its executor. #27884 then resolves those
registrations in the shared catalog.
`thread/start.selectedCapabilityRoots` can select a plugin root owned by
an executor, and Codex can resolve that package through the executor
filesystem. MCP declarations inside the selected plugin are still
ignored.
This PR adds the source-specific discovery layer on top of the
selected-plugin catalog boundary in #27884:
```text
selected capability root
|
v
resolve the plugin through its executor filesystem
|
v
read and normalize its MCP config through the same filesystem
|
v
contribute stdio registrations bound to that environment ID
```
The existing MCP launcher and connection manager remain unchanged. MCP
config parsing is shared with local plugins through #27863.
## What changed
- Added an executor plugin MCP provider in the MCP extension.
- Retained only the exact filesystem capability used for package
resolution and reused it for the selected plugin's MCP config, with no
host-filesystem fallback or unrelated process/HTTP authority.
- Read either the manifest-declared MCP config or the default
`.mcp.json`; a missing default file means the plugin has no MCP servers.
- Accepted stdio servers only for this first vertical. Executor-owned
HTTP declarations are skipped with a warning until their placement
semantics are defined.
- Normalized stdio registrations with the owning environment's stable
logical ID and plugin-root working directory.
- Resolved environment-variable names on the owning executor and
rejected explicit local forwarding for non-local plugins.
- Froze discovered declarations once per active thread runtime, then
applied current managed plugin and MCP requirements when contributing
them.
- Carried the selected root ID, display name, and selection order into
the catalog contribution defined by #27884.
## Behavior and scope
There is intentionally no production behavior change yet. This PR
provides the executor provider and contribution boundary, but app-server
does not install it in this change. Existing local plugin MCP loading is
unchanged, and no MCP process is launched by this PR alone.
## Assumptions
- The selected root ID is the plugin policy identity; the manifest
display name is presentation metadata.
- An environment ID is a stable logical authority. Reconnection or
replacement under the same ID does not change ownership.
- Selected plugin packages and their manifests are trusted inputs.
- The selected package and MCP discovery snapshot remain frozen for the
active thread runtime.
## Follow-up
The next PR installs this contributor in app-server and adds an
end-to-end test proving that a selected plugin MCP tool launches on its
owning executor, can be called by the model, survives an explicit MCP
refresh, and is invisible when its root was not selected.
Resume, fork, environment removal or ID changes, dynamic catalog reload,
and executor-owned HTTP MCP placement remain separate lifecycle
decisions.
## Verification
Focused tests cover executor-only filesystem reads, missing and
malformed config, stdio filtering and normalization, managed
requirements, package attribution, and selection order. CI owns
execution of the test suite.
## Why
**In short:** this PR resolves already-discovered MCP registrations. It
does not read selected plugins or discover their MCP servers.
The resolved MCP catalog currently builds config and auto-discovered
plugin registrations before runtime contributors are applied. A
thread-selected plugin needs a distinct precedence tier in that same
initial resolution pass: otherwise a disabled lower-precedence winner
can leave stale name-level state behind, and the winning MCP tools
cannot be attributed to the selected package reliably.
This PR adds that catalog boundary before executor discovery is
connected.
## What changed
- Added an explicit selected-plugin registration tier between
auto-discovered plugins and explicit config.
- Collected selected-plugin contributions before the initial catalog
build, while leaving compatibility and generic extension overlays in
their existing runtime phase.
- Retained the winning plugin ID and display name directly on
plugin-owned catalog registrations.
- Derived MCP tool provenance from the winning catalog entry instead of
joining against local-only plugin summaries.
- Retained the winning selected server's tool approval policy in the
running connection manager, so a selected registration cannot inherit
approval behavior from a losing local plugin.
- Kept remembered approval session-scoped for selected plugins until
there is an authority-aware persistence contract; Codex will not write
approval back to an unrelated local plugin.
- Preserved existing name-level disabled vetoes for discovered plugins
and config, while keeping a selected package's own disabled registration
scoped to that registration.
- Preserved deterministic selection order and existing config,
compatibility, and extension precedence.
The resulting order is:
```text
auto-discovered plugin
< selected plugin
< explicit config
< compatibility registration
< extension overlay
```
## Behavior and scope
This is a catalog and provenance change only. No production host
contributes selected-plugin MCP registrations yet, so existing local MCP
behavior remains unchanged.
The stacked follow-up, #27870, installs the executor plugin provider
that produces these registrations. App-server activation remains a
separate final step.
## Verification
Focused tests cover precedence, deterministic selected-plugin conflicts,
disabled-veto behavior across catalog phases, managed requirements
before selected-plugin resolution, winning-server approval policy, and
attribution when local and selected packages share an ID or server name.
CI owns execution of the test suite.
## Why
Clients that display or coordinate spawned subagents need an
authoritative snapshot of a thread's immediate spawned children when
they connect to app-server or recover after missing live events.
`thread/list` cannot query by parent, so clients must otherwise scan
unrelated threads or reconstruct relationships from rollout history and
transient events.
The direct spawn relationship already exists in persisted
`thread_spawn_edges` state. Review and Guardian threads do not
participate in that lifecycle and are intentionally outside this
filter's scope.
## What changed
This adds an experimental `parentThreadId` filter to `thread/list`.
Parent-filtered requests return direct spawned children from persisted
state while preserving the existing response shape, explicit filters,
sorting, and timestamp-only cursor behavior. The lookup does not read
rollout transcripts or recursively return descendants.
Supersedes #25112 with the narrower `thread/list` filter approach.
## How it works
1. An experimental client passes a valid thread ID as `parentThreadId`.
2. App-server routes the list through the existing thread-store and
state-database boundaries.
3. SQLite selects threads whose IDs have a direct persisted spawn edge
from that parent.
4. Omitted provider and source filters include all values; explicit
filters keep ordinary `thread/list` semantics.
5. Grandchildren, Review threads, and Guardian threads are excluded.
## Verification
State (144 tests), rollout (69 tests), and focused app-server
thread-list (31 tests) suites passed. Scoped Clippy checks and
repository formatting also passed. Coverage includes direct spawned
children, omitted grandchildren, pagination, malformed IDs, mixed source
kinds, explicit filters, and operation without rollout files.
## 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
## Why
We're moving to `PathUri` in more places to support cross-OS
app-server/exec-server, but we don't want to expose the URI encoding to
users of app-server's public APIs yet.
We'll need to translate at the app-server API boundary between
client-visible "regular" paths that are appropriate for the OS of the
environment for which the paths make sense, which means using the
environment's path personality to do the conversion.
`PathUri` doesn't yet attempt to encode environment ID, so for now we'll
sniff the most likely path convention for a given path.
## What
- Add `PathConvention` and `NativePathString` with host-independent
POSIX, Windows drive, and UNC rendering.
- Cover cross-host rendering, encoding, Unicode, invalid components.
## Why
Cross-OS tests in the wine environment will be much more faithful if we
can also test powershell integration.
## What
Add an x86_64 powershell binary to the bazel wine environment and
include smoke tests.
## Intent
Keep Bazel and Starlark files consistently formatted without requiring
contributors to install or version buildifier themselves.
## Implementation
- Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
v8.5.1.
- Run buildifier from the shared `just fmt` and `just fmt-check` driver,
with Windows-safe explicit DotSlash invocation.
- Provision DotSlash in formatting CI and contributor devcontainers, and
document the source-build prerequisite.
- Apply the initial mechanical buildifier formatting baseline.
## Summary
Prevent dependency refreshes from silently downgrading Codex's bundled
SQLite to a release affected by the WAL-reset corruption bug.
SQLx 0.9 accepts a broad `libsqlite3-sys` range. An unrelated lock
refresh therefore moved Codex from `libsqlite3-sys 0.37.0` back to
`0.35.0`, changing the bundled SQLite runtime from 3.51.3 to 3.50.2.
SQLite documents the affected versions and fix in [The WAL Reset
Bug](https://www.sqlite.org/wal.html#the_wal_reset_bug) and the [SQLite
3.51.3 changelog](https://www.sqlite.org/changes.html#version_3_51_3).
## 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>
## 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.
The first release after parallelizing Windows packaging moved the
critical path to the ARM64 packaging job:
https://github.com/openai/codex/actions/runs/27451157324
The x64 job started immediately and finished in 5m29s. The ARM64
job waited 76s for its runner and then took 5m56s, holding the
release for 1m43s after x64 had finished.
Packaging only downloads, signs, archives, and compresses already
built binaries. It does not execute target code. Run both packaging
jobs on x64 runners, keeping ARM64 hardware for compilation.
## 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.
## 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.
## 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.
## Why
Managed deployments need a reliable deny gate for remote control.
Persisted enablement and explicit startup requests currently remain able
to start the transport, while the removed `features.remote_control` key
is intentionally only a compatibility no-op.
This adds a dedicated requirement that administrators can use to force
remote control off without deleting the user's persisted preference.
Removing the requirement and restarting restores the prior choice.
## What Changed
- Added top-level `allow_remote_control` requirements parsing, sourced
layer precedence, debug output, and `configRequirements/read` exposure
as `allowRemoteControl`.
- Added a typed transport policy captured from the startup requirements
snapshot. Managed disable forces the initial state to disabled and
prevents enrollment, refresh, connection, and persisted-preference
mutation.
- Rejected every `remoteControl/*` RPC before parameter deserialization
with JSON-RPC `-32600` and `remote control is disabled by managed
requirements`.
- Preserved the existing disabled status notification and the previous
behavior when the requirement is `true` or omitted.
- Regenerated app-server protocol schemas and documented the new
requirement.
## Verification
- Confirmed all remote-control RPCs, including a malformed request,
return the managed-policy error while the initial status notification
remains `disabled`.
- Confirmed explicit ephemeral startup and persisted enablement make no
backend connection and leave the SQLite preference unchanged.
- Confirmed `allow_remote_control = true` does not enable or block
remote control and `configRequirements/read` returns
`allowRemoteControl: false` for the deny policy.
Related issue: N/A (managed-policy hardening).
## 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
```
## 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.
We should be able to test the cross-platform interactions for
exec-server, but we want to do this fairly soon and need a lightweight
option for testing. Using Wine to run the Windows side is far from
perfect, but it should give us a decent measure of how well we're
handling the basics of paths, process spawning, shell interaction, etc.
Future changes will add actual exec-server tests and possibly extensions
to the Wine testing environment.
## What
To make the cross-target-triple build easy, these tests are added only
to the Bazel build. This change adds an x86_64 Wine prebuilt managed by
Bazel and some build rules that can set up the needed toolchain
transition.
The support library for running Wine in a test environment created by
the Bazel rules comes with its own basic unit and integration tests.
Their primary priority is to make sure we don't leak child processes on
developer machines and that we can build and launch a basic hello world
binary.
## Validation
Confirmed these new tests are running on the [x86_64 bazel ubuntu
jobs](https://github.com/openai/codex/actions/runs/27446432302/job/81132356855?pr=27937):
```
//bazel/rules/testing/wine:wine-smoke-test (cached) PASSED in 3.7s
//bazel/rules/testing/wine:wine-test-support-unit-tests (cached) PASSED in 15.8s
```
## Context
Plugins can expose more than one way for Codex to use them: App
connectors for ChatGPT/SIWC-backed sessions and MCP servers for API key
login sessions. The broader goal is to make `PluginsManager` the place
that understands which plugin surfaces should be visible for the current
auth route, so callers do not each have to make that decision
themselves.
This PR is the small setup step for that work. It lets the plugin
manager be created with the current `AuthMode`, which gives the followup
auth routing PRs the information they need without relying on setter
injection.
## 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
- Let `PluginsManager::new_with_restriction_product` accept an initial
`AuthMode`.
- Keep `PluginsManager::new` behavior unchanged for ordinary callers.
## Validation
```bash
cargo test -p codex-core-plugins plugins_manager_tracks_auth_mode
cargo test -p codex-core list_tool_suggest_discoverable_plugins
git diff --check
```
---------
Co-authored-by: Xin Lin <xl@openai.com>
## Summary
- Keep local plugin suggestions bounded to fallback and explicitly
configured plugins.
- Preserve app-overlap recommendations for remote plugins using cached
catalog metadata.
- Remove the WSL-specific local discovery exception and move
manager-owned discovery tests into `codex-core-plugins`.
## Why
Local curated marketplaces were allowlisted before plugin detail
loading, so every uninstalled candidate could be deep-read before its
app IDs were checked. That caused per-turn reads of candidate plugin
manifests, skills, app configs, hooks, and MCP configs, which is
especially expensive on slow disks.
Remote discovery does not need those local candidate reads because app
IDs are already available in the cached remote catalog. Installed local
plugins are still loaded when needed to determine the user's installed
app IDs.
## Validation
- `just fmt`
- `just test -p codex-core-plugins discoverable::tests` (13 passed)
- `just test -p codex-core plugins::discoverable::tests` (4 passed)
- `just bazel-lock-update`
- `just bazel-lock-check`
- `git diff --check`
## Why
[#25345](https://github.com/openai/codex/pull/25345) was approved,
green, and squash-merged into its stacked base branch,
`fcoury/tokenmaxxing-api`. Four minutes later, that base branch was
force-pushed back to an API-only rebased head while preparing
[#25344](https://github.com/openai/codex/pull/25344) for `main`. As a
result, the squash commit from #25345 was orphaned and the TUI command
never reached `main` or a release.
This PR relands the orphaned TUI change from
[`411410b8`](https://github.com/openai/codex/commit/411410b85c2d8eb050d441f17396c5c4048d866f)
on current `main`.
## What changed
- Add `/usage`, `/usage daily`, `/usage weekly`, and `/usage cumulative`
for account token activity.
- Fetch account usage asynchronously through the existing
`account/usage/read` app-server RPC.
- Render daily, weekly, and cumulative activity with theme-aware
terminal palettes and bounded transient cards.
- Preserve transcript ordering while assistant streams, history
consolidations, active cells, and hooks complete.
- Hide `/usage` from completion when backend auth is unavailable while
keeping typed-command guidance.
- Carry current-main behavior forward for cwd-aware Markdown parsing,
Windows Terminal color detection, and personal access token auth.
- Clear pending usage cards on thread rollback and delay completed cards
until live hook output is committed.
- Add focused regression and snapshot coverage for loading, auth errors,
invalid views, rollback, hook ordering, layout, and charts.
## Prior review
The original implementation was approved by Eric Traut in #25345 after
testing multiple themes and light/dark terminals. This PR preserves that
reviewed implementation while adapting it to current `main` and adding
regression coverage for newer rollback and hook lifecycle behavior.
## Validation
- `just test -p codex-tui token_activity palette renderable
usage_command` — 37 passed.
- Focused rollback, hook-ordering, and error snapshot tests — 4 passed.
- `just fix -p codex-tui` — passed.
- `UV_CACHE_DIR=/private/tmp/codex-uv-cache just fmt` — passed.
- `cargo insta pending-snapshots` — no pending snapshots.
- `just test -p codex-tui` — 2,870 passed; two unrelated guardian
feature-flag tests failed because their expected `OverrideTurnContext`
event was absent:
-
`update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
-
`update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
- `just argument-comment-lint` could not complete because the local
Bazel LLVM `compiler-rt` repository is missing `include/sanitizer/*.h`.
The touched Rust diff was manually inspected and no missing
opaque-literal argument comments were found.
## Stack
1. [1 of 3] Support long raw TUI goal objectives - #27508
2. [2 of 3] Support long pasted text in TUI goals - #27509
3. **[3 of 3] Support images in TUI goals** - this PR
## Why
The first two PRs make goal definitions resilient to long text, but
`/goal` still dropped image inputs from the composer. That meant a user
could attach images while defining a goal and the resulting goal
continuation would not have any useful reference to those images.
Goal state still persists only objective text, so image inputs need to
become paths or URLs that the agent can read later.
## What Changed
- Extends TUI `GoalDraft` with local image attachments and remote image
URLs.
- Copies local goal images through the app-server filesystem layer into
the managed goal attachment directory, then rewrites active image
placeholders to file references.
- Appends unplaced local images and remote image URLs to the objective
as referenced image files or URLs.
- Preserves goal image metadata through live `/goal` submission and
queued `/goal` dispatch.
## Verification
- Added goal materialization coverage for local image files and remote
image URLs.
- Added/updated TUI slash-command coverage showing `/goal` drafts
include attached images instead of dropping them.
## Manual Testing
- Attached an image by bracketed-pasting its local path into a live
`/goal` composer. The `[Image #1]` placeholder became a server-host
`image-1.png` reference, copied bytes matched exactly, and no attachment
was written under the TUI's local home.
- Deleted an image placeholder before submitting a small goal and
verified no image was copied.
- Attached PNG and JPEG files to the same goal. Placeholder order was
preserved as `image-1.png` and `image-2.jpg`, and both remote copies
matched their source bytes.
- Tried extensionless, malformed-extension, and
extension/content-mismatched paths; the composer rejected them as image
attachments before goal dispatch rather than creating misleading managed
image files.
- Combined a local image, a large pasted block, and enough raw text to
exceed 4,000 characters. The remote attachment directory contained the
image, paste sidecar, and `goal-objective.md`; all embedded references
used server-host paths and both payloads matched their sources.
- Submitted an image replacement while a goal was active, verified no
image was copied before confirmation, then canceled and confirmed the
attachment count was unchanged.
## Why
We have some large gaps in our thread start, resume, and pre-sampling
traces that make it hard to tell where latency is coming from.
## What Changed
- Added coarse spans around thread start/resume, turn context
construction, rollout reconstruction, skill/plugin loading, and tool
preparation.
- Added a breakdown of discoverable-tool preparation across connector
loading, plugin discovery, and local plugin details.
## Testing
- `cargo check -p codex-app-server -p codex-core -p codex-core-skills -p
codex-core-plugins`
- Built the app-server locally and exercised thread start, first turn,
follow-up turn, server restart, thread resume, and a resumed turn.
In the release job from
https://github.com/openai/codex/actions/runs/27391514823
staging the nine npm release tarballs serially took 104 seconds.
Each package build writes to a separate staging directory, output path,
and npm cache. Run them through the script's existing thread pool,
bounded by the available CPU count. Delete each staging tree as its
build finishes so concurrency does not retain all copies until the end.
On ubuntu-24.04 in
https://github.com/openai/codex/actions/runs/27397232050
two serial trials took 103 and 101 seconds, while concurrent trials
both took 41 seconds. Comparing every extracted file from the first
serial and concurrent sets found no differences. This removes about one
minute from every release.
In the Windows x64 packaging job from
https://github.com/openai/codex/actions/runs/27391514823
building the primary and app-server package archives serially took 116
seconds.
Both archives read the same signed-binary directory but write separate
package trees and output files. Run them concurrently with xargs -P2.
The package helper rewrites DotSlash executables under the process temp
directory. A naive concurrent run failed when one process tried to
replace an executable used by the other. Give each bundle separate TMP
and TEMP roots to keep those caches independent.
On Windows x64 in
https://github.com/openai/codex/actions/runs/27397197944
three serial trials took 127, 128, and 126 seconds. Concurrent trials
took 76, 74, and 74 seconds, saving 52 to 54 seconds. This removes about
50 seconds from the release critical path without changing the packaging
commands or output set.
## 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.
In the x64 packaging job from
https://github.com/openai/codex/actions/runs/27391514823
archiving and uploading PDBs took 65 seconds after signing. Release
packaging could not start until that work completed.
Windows code signing changes executables but not their PDBs. Package
the PDBs in a sibling Ubuntu job as soon as all binary artifacts are
available. Signing and release packaging can then proceed without
waiting for the symbols archive, reducing the critical path by about
one minute.
## 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.
## Summary
This PR promotes Mentions 2.0 (unified TUI mention popup) to stable and
enables it by default.
- Keep `mentions_v2` as a temporary rollback path to the legacy split
popups (`--disable mentions_v2`).
- Add feature-default and snapshot coverage for the default experience.
## Prior work
- [#19068 — Unified mentions in
TUI](https://github.com/openai/codex/pull/19068)
- [#22375 — Use plugin/list to get plugins for
mentions](https://github.com/openai/codex/pull/22375)
- [#23363 — Unified mentions tweaks and rendering
polish](https://github.com/openai/codex/pull/23363)
## Test plan
- Launch Codex without any feature overrides.
- Type `@` in the TUI composer.
- Confirm the unified mentions menu opens and displays filesystem,
plugin, and skill results.
Each Windows packaging job creates three compressed forms of five
binaries in sequence. This takes roughly two minutes and is on the
release critical path.
Use two xargs workers to compress independent binaries concurrently.
The workers only read the raw executables and write per-binary archive
names. The Codex zip can safely read the helper executables while their
own archives are generated.
On a 16-vCPU AMD EPYC 9V74 Windows x64 release runner, alternating
trials against artifacts from release run 27391514823 measured:
serial: 121 s, 123 s, 121 s
parallel: 73 s, 73 s, 74 s
This saves 47 to 50 seconds in the x64 packaging lane, reducing the
observed release critical path by about 48 seconds when x64 remains the
limiting lane.
https://github.com/openai/codex/actions/runs/27401905938
Codex seems to do interesting things with `cfg`'s sometimes and it seems
it would be good to give it guidance about how broadly our Rust needs to
work.
This adds a very brief section to AGENTS.md explaining that we target
the major desktop OSes and that we want the vast majority of our logic
to be portable across them.
## Why
We need request-level evidence for Guardian cases where
`codex-auto-review` is missing from the client-side model catalog and
the review falls back to the parent model.
## What changed
- Add `guardian_catalog_contains_auto_review` to Guardian Responses API
client metadata.
- Add `guardian_model_provider_id` to Guardian Responses API client
metadata.
- Keep review-session metadata optional so callers without metadata
preserve the existing `None` path.
- Add tests for override, normal preferred-model, and
missing-auto-review-catalog behavior.
## Validation
- `just test -p codex-core
guardian_review_records_missing_auto_review_model_in_request_metadata`
- `just test -p codex-core
guardian_review_uses_model_catalog_override_when_preferred_review_model_exists`
- `just test -p codex-core
guardian_review_uses_preferred_review_model_without_model_catalog_override`
- `git diff --check origin/main`
## Stack
1. [1 of 3] Support long raw TUI goal objectives - #27508
2. **[2 of 3] Support long pasted text in TUI goals** - this PR
3. [3 of 3] Support images in TUI goals - #27510
## Why
Large text pasted into the TUI composer is represented as a paste
placeholder plus pending paste metadata. For `/goal`, preserving only
the visible placeholder is not enough: the agent would see a short
placeholder string instead of the actual pasted text, and the long-text
support from the first PR would never see the payload.
The TUI also needs to avoid writing stale sidecar files when a user
pastes a large block and then deletes its placeholder before submitting
the goal.
## What Changed
- Introduces a TUI `GoalDraft` for goal submissions so `/goal`, `/goal
edit`, and queued goal commands can carry objective text plus text
elements and pending paste payloads.
- Materializes active pasted-text placeholders to `pasted-text-N.txt`
files through the app-server filesystem path introduced in #27508.
- Rewrites active paste placeholders in the persisted objective to file
references, while leaving literal placeholder-looking text alone.
- Filters out deleted paste placeholders so otherwise-small goals do not
require `$CODEX_HOME` or remote filesystem writes.
- Preserves pending paste metadata when a `/goal` command is queued
before a thread exists.
## Verification
- Added goal materialization tests for active paste placeholders,
deleted paste placeholders, and whitespace-only paste payloads.
- Added/updated TUI slash-command tests for large pasted text, queued
`/goal` commands before thread start, and queued oversized goal
behavior.
## Manual Testing
- Used real terminal bracketed-paste sequences through a remote TUI
session. A 1,228-byte multiline paste became `pasted-text-1.txt`; its
first/last lines and byte count matched exactly, and the persisted
objective referenced the server-host path.
- Pasted a large block, deleted its placeholder, and submitted a small
replacement objective. No new directory or sidecar file was created.
- Added two same-length large pastes to one goal. The composer
disambiguated their visible placeholders, and materialization preserved
order and contents in `pasted-text-1.txt` and `pasted-text-2.txt`.
- Submitted a whitespace-only large paste and verified the goal was
rejected as empty without writing a file.
- Submitted a pasted-text replacement while another goal was active,
verified no file was written before confirmation, then canceled and
confirmed the original goal remained unchanged.
- Combined a large paste with enough raw text to exceed 4,000 characters
after placeholder rewriting. The paste sidecar and `goal-objective.md`
were created in the same remote attachment directory, and `/goal edit`
restored the rewritten objective with its sidecar reference.
## 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).
## Summary
- store MCP OAuth credentials in the configured auth credential backend
- support encrypted-local OAuth storage, including legacy keyring
migration
- propagate the credential backend through MCP refresh, session, CLI,
and app-server paths
## Stack
1. #27504 — config and feature flag
2. #27535 — auth-specific secret namespaces
3. #27539 — encrypted CLI auth storage
4. this PR — encrypted MCP OAuth storage
This is a parallel review stack; the original #17931 remains unchanged.
## Tests
- `just test -p codex-rmcp-client` (the transport round-trip test passed
after building the required `codex` binary and retrying)
- `just test -p codex-mcp`
- `just test -p codex-app-server
refresh_config_uses_latest_auth_keyring_backend`
- `just test -p codex-core
refresh_mcp_servers_is_deferred_until_next_turn`
- `just test -p codex-cli mcp`
- `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
-p codex-app-server -p codex-protocol`
- `just bazel-lock-check`
## Summary
Startup warnings for under-development features only recognized bare
boolean toggles like `features.foo = true`. An upcoming feature will use
table-format config, so `features.foo = { enabled = true, ... }` needs
to count as an explicit opt-in too.
This updates the warning predicate to recognize structured tables with
`enabled = true`, while leaving tables without that field unwarned.
## Testing
- `just fmt`
- `just test -p codex-features
unstable_warning_event_mentions_enabled_structured_under_development_feature`
## 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`
## Why
Removes the realtime audio support from TUI.
## What Changed
- Removed the TUI `/realtime` and realtime `/settings` command paths.
- Deleted TUI voice capture/playback, WebRTC session handling,
audio-device selection UI, and recording-meter code.
- Removed TUI realtime tests and snapshots that covered the deleted
surfaces.
- Dropped the TUI-only `cpal` and `codex-realtime-webrtc` dependencies
and refreshed the Rust/Bazel locks.
## 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`