## What changed
- Add `ToolLifecycleContributor::on_mcp_tool_result` with access to the
executed MCP tool context, rewritten arguments, extension data stores, and
mutable server result.
- Run contributors before publishing MCP completion and before preparing the
result for the model, so extensions can inspect or replace successful and
error results.
## Testing
- Cover unchanged, replaced, and error results for direct MCP calls, plus
replaced and error results through Code Mode.
- Verify completion waits for result processing and the processed result flows
to both completion events and subsequent model input.
GitOrigin-RevId: ece7acfe9c5eed4778cb78a29c3ffd06a74e1bcf
## Why
Plugin MCP trust decisions must describe the exact host-discovered server
registration. Looking up a plugin again by ID can resolve a different root.
## What changed
- Retain the host plugin root in MCP catalog attribution and connection identity.
- Reconnect an MCP server when its attributed plugin root changes.
- Pass the retained root through tool lifecycle attribution and use it directly
when building trusted tool context.
## Testing
Add coverage for catalog identity, connection reconciliation, lifecycle
attribution, and rejecting a different root associated with the same plugin ID.
GitOrigin-RevId: bc92743dd40ab44065a07c3ea972b4956ef8351f
## What changed
- Add callback lifetimes to extension `ToolCall`, `ToolEnvironment`, turn-input context, and skill-read request types.
- Require extension tool executors to handle calls for any invocation lifetime and tie their returned futures to that lifetime.
- Update built-in extensions, adapters, and tests to use the lifetime-scoped APIs without retaining host-owned environment capabilities.
GitOrigin-RevId: aca9ce3a1b6870df550bc84ec9308cc65533f161
## What changed
- Add optional `McpToolContext` metadata to `ToolStartInput`, exposing the
model-visible MCP tool details and its source classification without exposing
the executable client.
- Classify MCP calls as connectors, configured servers, plugin servers,
executor-selected plugins, or other registrations based on the prepared call.
- Prepare each MCP call before notifying tool lifecycle contributors and reuse
that same call for execution so the callback describes the call that runs.
## Testing
- Cover host-owned connector calls and extension-owned Apps server calls,
including their distinct provenance and executed tool names.
GitOrigin-RevId: efd23f511b1045ffdd96349d621ad62365569b2b
## What changed
Rename the `UserInstructions` type to `Instructions` across the extension API,
its core API re-export, and all consumers. This keeps loading behavior and the
`LoadedUserInstructions` provider interface unchanged.
GitOrigin-RevId: 1ac236b34c31718e7aa38ff5eae5e9b179aeedd8
## Why
Selected executor plugin roots could still expose capabilities when managed
requirements disabled the `plugins` feature.
## What changed
- Suppress MCP servers, skills, apps, and connectors from selected executor
plugins when `plugins` is disabled, while preserving the selected-root
identity used to filter those capabilities.
- Apply the policy consistently to direct selected-root discovery and batched
executor capability discovery.
## Testing
- Add contributor-level and app-server coverage that verifies disabled plugin
capabilities are absent and their MCP servers never start.
GitOrigin-RevId: e30bd8a936c4b0e083908b89a65f4d02e01747de
## What changed
- Add the under-development `skip_host_skill_discovery` feature to bypass host
skill snapshots during session warmup and turn setup.
- Let skill invocation contributors declare whether they require host-owned
skills. Preserve host discovery when no contributors are registered or any
contributor requires it.
- Keep executor and orchestrator skill catalogs and instruction loading
available when host discovery is skipped.
## Testing
- Cover executor-only, orchestrator, mixed-contributor, and legacy host-skill
behavior.
GitOrigin-RevId: 48169403090b234e1a304c6523633fd867df454a
## What changed
- Add a `ContextWindow` prompt slot for thread-scoped extension contributions.
- Include fragments in that slot in the token-budget context-window message.
- Keep the MCP `notes/thread_hint` bridge when the native history-notes extension is disabled, without falling back to it after a native request failure.
GitOrigin-RevId: befcb7cc8570b846094b2b542a112b186b39a30a
## What changed
- Split the approval review contributor into a `fast_decision` path for existing evidence and a `full_review` path for structured reviews.
- Add host-owned full-review inputs with action evidence, conversation history, thread and turn identifiers, approval context, and extension data.
- Add structured assessment and error types, and resolve full reviews using the first contributor that claims the request.
## Testing
- Cover full-review input propagation, first-claim ordering, and short-circuit behavior in the extension registry tests.
GitOrigin-RevId: 6f763d874af8c0b66a0d760b7b7c7dfe32251c9e
## What changed
- Add `ToolCallSource` to extension `ToolCall`s and propagate direct or Code Mode
invocation metadata, including the runtime cell and nested tool-call IDs.
- Add `ToolCall::response_byte_budget` so extension tools can bound direct-call
responses by the host truncation policy while allowing Code Mode responses up
to the tool's own limit.
- Share the skills extension's response-size limit between its `list` and `read`
tools.
## Testing
- Verify that Code Mode source metadata is preserved when core constructs an
extension tool call.
GitOrigin-RevId: b10aef01be30d35b67bee3af24a1a4175c9ee112
## What changed
- Carry each contextual fragment's `ContentItemKind` through initial context assembly and world-state updates.
- Attach the classifications to merged response messages through `content_item_kinds`, preserving their order alongside the message content.
- Convert extension prompt fragments into annotated developer fragments and classify built-in developer and guardian instructions.
## Testing
- Add an integration test covering roles and content annotations in the first request.
- Update world-state and persisted-history tests to account for annotation metadata.
GitOrigin-RevId: 71c48dad5b17bffd47ba03263e3eb6d5e74fbf4b
## What changed
- Require each `PromptFragment` to include a producer-owned `ContentItemKind`.
- Re-export `ContentItemKind` from `codex_extension_api` and assign stable kinds to skills, memories, examples, and test contributors.
- Limit prompt slots to developer policy and developer capabilities, and combine contributed fragments into a single developer update.
## Testing
- Update extension registry tests to verify fragment ordering with the new content-kind metadata.
GitOrigin-RevId: ef8624a9b4ae9dce025bca9c0954d91dd8075e85
## What changed
- Retain bounded evidence from completed synchronous Guardian allow and deny reviews and supply it to subsequent Guardian v2 async classifier samples as trusted developer context.
- Keep review evidence isolated from the conversation transcript, escape and truncate its fields, and ignore failed or incomplete reviews.
- Invalidate retained evidence after conversation history rewrites or new user messages, including authorization changes in a worker's root thread.
## Testing
- Cover approved, denied, malformed, and forged review inputs, plus root rollback and authorization-change scenarios.
GitOrigin-RevId: 27817e1fde9a136de727048c4220d148fcf72f42
## What changed
- Add an extension API for spawning host-owned internal sessions and a
`ThreadReadyInput` lifecycle callback that runs after thread registration.
- Add Guardian reviewer session scaffolding that records the parent thread and
effective model, plus the under-development `guardian_ext` feature flag.
- Start internal sessions with fresh history while preserving parent lineage,
shared session controls, and internal-thread visibility rules.
- Scope internal-session prompt cache keys to their source and parent thread,
and expose `guardian` as an internal session source.
## Testing
- Cover internal-session spawning, parent metadata, history isolation, prompt
cache keys, and extension spawner argument forwarding.
GitOrigin-RevId: 682dae80397d62bb36247796b447042e760ca364
## What changed
- Add environment-provided MCP restrictions for configured and plugin-provided servers.
- Disable attachment-scoped servers while their environment configuration is pending, failed, or unselected, while preserving selected-plugin access and the controller-owned Apps server.
- Apply the resolved policy to runtime startup, model tool exposure, telemetry, OAuth flows, and skill dependency installation.
## Testing
- Cover policy filtering for configured and plugin servers, environment state transitions, Apps ownership, and managed OAuth rejection.
GitOrigin-RevId: 7ca5d4dda14068e758497f88835d5cba685e159e
## What changed
- Carry selected plugin identities from MCP discovery into per-turn extension data, limited to capability roots that are ready for the turn.
- Annotate executor skill catalog entries with the matching plugin ID and `user` scope.
- Include that attribution in `skill_invocation` analytics for both explicit and implicit executor skill invocations.
## Testing
- Extend the executor skills app-server test to verify plugin ID, skill scope, and invocation type for explicit and implicit analytics events.
GitOrigin-RevId: 9345600ae29bcb8eb894fffd2d6f7b05f22ebf74
## What changed
- Pass metrics bound to the active turn's model to approval-review contributors.
- Add counter support to `ExtensionMetrics` while preserving host session attribution.
- Record Guardian v2 tool-call score lag and count stale-score fallbacks with the `score_lag` reason.
## Testing
- Cover counter forwarding and session metadata tags.
- Verify Guardian v2 metric samples for current and stale scores.
GitOrigin-RevId: 0ec34736d29d2e37ed217a27e773b4f6230ae38a
## What changed
- Give approval review contributors the first opportunity to resolve an action when automatic review is not required for the model.
- Return an extension's decision directly and fall back to Guardian when no extension claims the review.
- Update Guardian V2 to approve actions below its risk threshold through the review contributor API, and remove the extension approval-policy API.
## Testing
- Verify that an extension approval bypasses both the Guardian model call and the user approval prompt.
- Cover Guardian V2 review decisions for low-risk, high-risk, and disabled threads.
GitOrigin-RevId: 5be56941143aff6f358b75b5f8101b7c047147ae
## Why
Concurrent Guardian samples can finish out of order, allowing an older sample to
replace the thread's newer in-memory security risk score.
## What changed
- Record when each `SecurityRiskScore` sample starts while preserving compatibility
with records that have no timestamp.
- Add `ExtensionData::insert_if` so checking and replacing a typed attachment happen
under the same lock.
- Update Guardian V2 to replace the thread's risk score only when the completed sample
is newer than the stored one.
## Testing
- Verify concurrent conditional inserts retain the newest value.
- Verify Guardian-generated risk scores include a sampling timestamp.
GitOrigin-RevId: 6543a4e5497da4c623331050789e91613f931c4b
## What changed
- Add a thread-scoped extension API for requesting automatic review, resolving
multiple contributors to the strongest requirement.
- Have Guardian v2 request automatic review when it is enabled and the latest
`action_risk` score is at least `0.8`.
- Honor extension review requirements across tool approvals, MCP elicitation,
and remote execution while preserving host configuration and managed-policy
constraints.
## Testing
- Cover contributor registration and requirement resolution.
- Verify Guardian v2 requests review at the threshold, returns to the default
below it, and does not affect threads where the extension is disabled.
- Exercise extension-requested strict review with a remote model override.
GitOrigin-RevId: 6329fc23585d5cc7173c39bb9952c3109ec3884b
## What changed
- Invoke `ToolLifecycleContributor::on_tool_start` only after pre-tool hooks
have finalized the invocation.
- Pass hook-rewritten arguments and the post-hook conversation snapshot to the
callback.
- Skip the start callback when a hook denies execution or supplies input that
cannot be applied.
## Testing
Add lifecycle tests covering rewritten input, hook-added context, denied tool
calls, and invalid rewritten input.
GitOrigin-RevId: 936efaf4a8c35321f9982ff2a33bb8bece1ebf39
## Why
Guardian V2 needs the requested action and its conversation context to assess
risk; a tool name and call ID alone do not describe what the tool will do.
## What changed
- Expose the original, pre-hook `ToolPayload` to tool lifecycle contributors.
- Build Guardian V2 classifier input from the bounded conversation transcript
and a structured planned-action object containing the tool name and arguments.
- Warn and skip classification if the planned action cannot be serialized.
## Testing
- Verify lifecycle contributors receive tool arguments.
- Verify Guardian V2 sends transcript history and planned-action JSON to the
classifier.
GitOrigin-RevId: 28266ef7c1d2267ba705fcfbd9be4ef48e64e104
## What changed
- Add a shared, read-only `ConversationHistorySnapshot` capability to the extension API.
- Include the snapshot in `ToolStartInput`, preserving conversation order while excluding contextual user messages.
- Avoid acquiring a history snapshot when no tool lifecycle contributors are registered.
## Testing
- Verify snapshots share existing response items, remain stable as history changes, and filter contextual user messages.
- Verify tool-start callbacks receive the history available for successive tool calls.
GitOrigin-RevId: 822cae33781dd876ea628f104be08d8a449a4900
## What changed
- Add a `SecurityRiskScore` rollout item containing a category and numeric score.
- Persist the item in both thread history modes while excluding it from model context, user-visible thread history, search text, forks, and reconstructed conversation history.
- Re-export the score type from the extension API.
## Testing
- Cover serialization, persistence and loading, thread history projection, session reconstruction, append planning, and memory filtering.
GitOrigin-RevId: 1926fe366aeaa75052708a6da589f45a38eefb52
## Why
Converting each environment's working directory to a host-native path caused
turn-input contributors to omit environments with foreign path formats.
## What changed
- Expose `TurnInputEnvironment.cwd` as a `PathUri`.
- Pass every turn environment to contributors without host-path conversion.
## Testing
Added an integration test that submits a foreign working-directory URI and
verifies the contributor receives the environment unchanged.
GitOrigin-RevId: a122526be47f5331614381e9c6e4b85087a96bed
## What changed
- Add a storage-neutral queue extension for listing, adding, editing, reordering, deleting, and explicitly starting queued user messages.
- Dispatch queued messages in FIFO order when a thread becomes idle after a completed turn, while leaving them queued after interruptions or failures.
- Wait for user input to be persisted before removing its queue item, and report hook rejection, task termination, and persistence failures distinctly.
- Snapshot local image and audio attachments into validated data URLs before storing them, enforce input limits, and emit `ThreadQueueChanged` events after mutations.
## Testing
- Cover queue mutations, automatic and explicit dispatch, interruption and failure behavior, hook rejection, invalid records, input limits, and local attachment snapshotting.
GitOrigin-RevId: 14f39d7c8cbaccb767a6da84b82cced0edfcb4b9
## What changed
- Add `session_source()` to `McpServerContributionContext` for thread-scoped MCP resolution.
- Propagate each thread's `SessionSource` through initial setup, per-step resolution, and runtime refreshes.
## Testing
- Verify global contexts omit the source while thread-scoped contexts retain it.
- Verify MCP contributors observe distinct sources for a root thread and its spawned subagent.
GitOrigin-RevId: 23da1509858e023fbd9e2d179034ddc83553f17b
## What changed
Make `ExtensionRegistryBuilder` wrap an `ExtensionRegistry` and register
contributors directly on it. Return the wrapped registry from `build()` instead
of copying each field into a new value.
GitOrigin-RevId: ae04de99c17ea951156f9892151b4196c70026bf
## What changed
- Add an optional `ExtensionMetrics` capability to thread, turn-input, and world-state contributors, backed by session telemetry in the host.
- Record skill counts, omissions, and truncated description characters for extension-rendered catalogs, tagged by the catalog surface.
- Preserve host session attribution on extension metrics, including the effective model for each turn, and prevent extension tags from overriding host metadata.
- Avoid duplicate host-catalog samples by recording world-state metrics only when the section is published or changes.
## Testing
- Cover metric values and surface tags, session metadata preservation, turn-level model changes, and host world-state publication behavior.
GitOrigin-RevId: 9059e521943a8f81df25a38b03692eddbc1d990d
## What changed
- Add executor authority support to `skills.list` and `skills.read` for skills from selected capability roots.
- Allow `skills.read` to load package-relative resources referenced by an executor skill while keeping reads within the selected package.
- Paginate skill listings and resource contents with bounded response sizes and stale-cursor validation.
- Resolve executor filesystems per sampling step so skill tools use the active selected capability roots.
## Testing
- Extend the app-server executor skill test to list an executor skill, read its main `SKILL.md`, and read a paginated reference resource.
- Cover visibility filtering, metadata bounds, pagination, invalid cursors, package boundaries, and resource size limits.
GitOrigin-RevId: 1b5de95d5d1b9f79c34d2879d6e67f24401e9c29
## Why
Extension warnings need a stable thread target even when they are emitted outside an active turn.
## What changed
- Add `ExtensionWarning` and a dedicated `ExtensionEventSink::emit_warning` path with an explicit thread ID and optional turn ID.
- Deliver extension warnings as thread-scoped app-server `warning` notifications, preserving listener ordering and waiting briefly for a subscriber when no listener is active.
- Move skills warnings to the new path, limiting provider catalog warnings to four messages of 256 bytes each.
## Testing
- Cover warning routing, ordering, UTF-8-safe truncation, subscriber fallback, and invalid thread IDs.
- Verify skills warning bounds and app-server delivery of executor catalog budget warnings.
GitOrigin-RevId: 4dcb1ba7f17c0edb1a9d548f9568e0c734bffc15
## What changed
- Remove the step-scoped `ExtensionData` argument from context, turn-input, and tool contributors.
- Pass the host's optional `McpResourceClient` through `ThreadStartInput` so extensions can retain session capabilities explicitly.
- Keep the MCP resource client in skills-owned session state for catalog loading and skill tools.
GitOrigin-RevId: bafa77bcd998aff408d6a396c5fd9ac268c4cce4
## What changed
- Add an `ExtensionData` store to each `StepContext`.
- Pass the step store to context, world-state, turn-input, and tool contributors so extensions can use capabilities bound to the current sampling step.
- Preserve the captured step context when rebuilding initial context during compaction.
GitOrigin-RevId: 51f3105d3a793f60534f122f9be8247c131c2ca5
## What changed
- Change `ReviewDecision::Denied` to carry a rejection string and update the generated protocol schemas.
- Preserve specific rejection reasons through command, patch, network, MCP, delegated, and automatic approval flows so tool results can return them to the model.
- Distinguish invalid approval responses from user declines and truncate oversized rejection messages before adding them to model context.
## Testing
- Add coverage for denial serialization, invalid app-server approval responses, dropped automatic reviews, network rejection propagation, and rejection-message truncation.
GitOrigin-RevId: d48a55643aa03c7ecfa08a8e4b669913eec5464a
## Why
Selected capability roots can contribute plugins, MCP servers, connectors, and
skills. Discovering each contribution separately requires repeated access to the
executor filesystem.
## What changed
- Add the `capabilityRoots/discoverV1` exec-server RPC to scan selected roots and
materialize recognized plugin manifests, configuration files, skill
instructions, and skill metadata in one bounded request.
- Add the opt-in `executor_capability_discovery` feature, with a thread-scoped
cache and per-step snapshot shared by MCP and skill discovery.
- Parse MCP, connector, and skill contributions from the materialized snapshot,
including serving cached skill instructions without another filesystem read.
## Testing
- Cover discovery limits, manifest precedence, root-local failures, cache reuse,
plugin contributions, and parity with the existing environment skill loader.
GitOrigin-RevId: f98fd2321cafb58c596db02da1f83c09d8eb375d
## Why
Standalone web search and image requests need to preserve the trusted,
thread-scoped originator used for billing attribution.
## What changed
- Seed extension data with the resolved thread originator and recognize
`codex_work_cca` and `chatgpt_cca` service names as originators.
- Forward non-default originators from the web search and image generation
extensions with the shared originator-header helper.
## Testing
- Cover originator resolution, header validation, and forwarding for web
search, image generation, and image editing.
GitOrigin-RevId: 165dace8e4d24e4bd92f234bff5ec77a21a7ff6e
## What changed
- Let deferred environments provide selected capability roots with their ready signal.
- Validate that those roots have unique, non-empty IDs, belong to the registering environment, and stay within the root limit.
- Include roots from ready turn environments when resolving MCP contributions, and refresh the MCP runtime when the selected root set changes.
- Expose the exact ready root set to MCP contributors so executor plugins become available with their environment.
## Testing
- Cover ready-root propagation, validation failures, replacement isolation, reconnection, and MCP plugin availability refresh.
GitOrigin-RevId: ec3498aab1164824025094e96a9b1063b7b731ad
## What changed
- Add `SkillInvocationContributor` and registration support to the extension API.
- Provide invocation callbacks with session-, thread-, and turn-scoped extension data, the turn ID, skill resource, and explicit or implicit invocation kind.
- Notify registered contributors when Codex observes a deduplicated implicit skill invocation.
## Testing
- Extend the extension registry round-trip test to cover skill invocation contributors.
GitOrigin-RevId: f428336728889d9bf4fba41a7dd8d3a6f9b9728c
## Summary
- Forward Codexs canonical `originator` header on ChatGPT-hosted Apps
and plugin-runtime MCP requests.
- Preserve the configured `X-OpenAI-Product-Sku` header.
- Cover originator-only and originator-plus-SKU configurations.
## Why
Sites project creation is logged downstream of Apps MCP. Production
validation found `CODEX_UNKNOWN_DEFAULT` project-created threads that
matched `codex_surface=desktop_app` and `originator=Codex Desktop` in
`fact_codex_cli`
([query](https://kepler.gateway.data-1.internal.api.openai.org/permalink/H_mVoVPqLQ0)).
The hosted Apps MCP configuration forwarded the product SKU but not
Codexs canonical originator, so codex-backend could not derive
`CODEX_DESKTOP_APP` for those tool calls.
## Validation
- `just fmt`
- `CARGO_HOME=/private/tmp/codex-cargo-home
CARGO_TARGET_DIR=/private/tmp/codex-target just test -p codex-mcp` (106
passed)
- `CARGO_HOME=/private/tmp/codex-cargo-home
CARGO_TARGET_DIR=/private/tmp/codex-target just fix -p codex-mcp`
## Why
Generic plugin guidance is currently emitted only with initial context
from host plugin state. An executor-selected plugin can become available
later in the same turn, making its skills and tools usable without ever
telling the model how plugin capabilities should be used.
## What
- project every ready selected plugin package, including skill-only
plugins
- carry plugin availability with the exact MCP runtime projection while
preserving MCP manager reuse when servers and connectors are unchanged
- move generic plugin guidance from the static initial-context path into
persisted World State
- recognize legacy and retained plugin fragments so resume and
compaction do not duplicate guidance
## Testing
- `just test -p codex-mcp-extension`
- `just test -p codex-core plugins_instructions`
- `just test -p codex-core
plugin_availability_change_reuses_the_mcp_manager`
- `just test -p codex-app-server --test all selected_capabilit`
## Why
World State restores its structured snapshot on resume so unchanged
sections do not have to be rendered again. That is safe only when the
model-visible fragment represented by the snapshot is still present in
retained history.
For selected executor skills, the failing selected-capability scenario
exposed this state:
```text
persisted World State: selected skill catalog is known
retained model history: selected skill catalog message is missing
next diff: unchanged, so emit nothing
```
The model resumes without being told about the selected skill catalog.
## What changed
World State contributions may now optionally describe the concrete
model-visible fragment that must remain in retained history.
When a persisted snapshot is present:
```text
matching retained fragment exists -> trust snapshot, emit nothing
matching retained fragment missing -> treat section as absent, render current state once
```
The skills extension uses this for non-empty selected-environment
catalogs by matching its exact rendered catalog body. Empty or hidden
catalogs do not require a fragment.
## Scope
This does not clear or rebuild the whole World State baseline. It does
not change skill discovery, cache invalidation, environment
availability, or MCP runtime behavior. It only keeps a persisted section
snapshot and its retained model context consistent across resume/history
reconstruction.
## Coverage
A focused World State regression test verifies both sides:
- a missing retained fragment is rendered again
- a matching retained fragment avoids duplicate injection
## Why
Selected plugin metadata is stable, but MCP processes are live runtime
state. They need different lifetimes:
- the MCP extension caches manifest, MCP, and connector declarations for
each stable selected root;
- each model step projects that cached metadata through the roots that
resolved as ready for that exact step;
- the MCP manager is rebuilt only when that availability projection
changes.
This matches executor skills: both features consume the same resolved
step roots instead of inferring readiness from the turn's selected
environments.
## Behavior
```text
E1 not ready for this step
-> no E1 MCP servers or connectors
-> cached plugin metadata stays in ext/mcp
E1 becomes ready
-> reuse cached metadata
-> publish one MCP runtime containing E1 capabilities
same ready roots on the next step
-> reuse the exact runtime; no rediscovery and no MCP restart
resume
-> create new extension thread state and a new MCP runtime
```
All model-facing consumers use the same step snapshot:
```text
resolved selected roots
|
v
extension MCP/connector projection
|
v
{ MCP config, connector snapshot, MCP manager }
|
+-> advertise model tools
+-> build app/connector tools
+-> execute MCP calls
```
## Cache contract
The existing MCP extension owns a cache keyed by the full
`SelectedCapabilityRoot`:
```rust
let state = thread_store.get_or_init(SelectedExecutorPluginMcpState::default);
```
The cache lives with extension thread state. Environment availability
filters projection but does not invalidate metadata. Resume creates new
thread state. There is no file watcher or executor generation because
contents behind a stable environment/root are assumed stable.
## What changes
- Keeps executor plugin discovery and cached metadata in `ext/mcp`.
- Caches MCP and connector declarations together per selected root.
- Uses the step's already-resolved capability roots, including lazy
environments that are not turn environments.
- Reuses the current MCP runtime when the ready-root projection is
unchanged.
- Uses the same step MCP manager and connector snapshot for
model-visible tools and execution.
- Resolves direct thread-scoped MCP requests from the current
selected-root projection.
## Deliberately out of scope
- `app/list` remains based on the latest global host-plugin state; this
PR does not make its response or notifications thread-specific.
- `required = true` startup semantics do not apply to delayed executor
MCP activation.
- No filesystem/content invalidation.
- No transport-disconnect watcher.
- No executor generations or environment replacement semantics.
- No client sharing across complete manager replacements.
## Stack
1. Extension-owned World State sections.
2. Project executor skills through World State.
3. Pin one MCP runtime to each model step.
4. **This PR:** project selected MCP and connector state from
extension-owned metadata.
5. Integration coverage for selected capability availability and resume.
## Verification
-
`selected_plugin_servers_use_managed_requirements_for_the_selected_root_id`
- The stacked integration PR covers unavailable to ready activation,
unchanged-runtime reuse, skills, MCP tools, connector attribution, and
cold resume.
## Why
A selected executor environment can be unavailable in one model step and
ready in the next. The model should see its skills only while that
environment is ready, without rescanning stable files on every sample.
The product assumption is simple:
- an environment ID names one stable logical environment;
- the selected root contents do not change during the thread.
## Behavior
```text
E1 unavailable -> do not show E1 skills
E1 ready -> discover once, cache, show through World State
E1 unavailable -> hide skills, keep cache
E1 ready again -> reuse cache, show skills again
resume -> create a new thread cache and discover again
```
The cache key is the full `SelectedCapabilityRoot`. Availability does
not invalidate it; dropping the extension's thread state does.
The step supplies the ready selected roots directly. They do not have to
be turn environments:
```text
turn environment: laptop
selected root: worker:/plugins/lint-fix
worker ready -> lint-fix skills are visible
```
## What changes
- Keeps executor skill catalogs in the existing skills extension.
- Passes the roots resolved as ready for the step into World State
contributors.
- Loads each ready selected root at most once per thread.
- Contributes the executor catalog as the `skills` World State section.
- Uses the exact step catalog for explicit skill selection and body
reads.
- Leaves host and orchestrator skill behavior where it already lives.
Taking a step snapshot itself does not add an RPC. Executor filesystem
calls happen only on the first discovery of a stable root for that
thread.
## What does not change
- No filesystem watcher or content-based invalidation.
- No retry/generation framework.
- No skill runtime migration into core.
- No general rewrite of the skills extension.
## Stack
1. Extension-owned World State sections.
2. **This PR:** project cached executor skills through World State.
3. Pin one MCP runtime to each model step.
4. Project selected MCP/app/connector metadata by environment
availability.
5. One end-to-end integration scenario.
## Why
#29856 already owns the durable thread intent and exact environment
binding. This PR adds only the small missing extension boundary: an
extension can contribute one named World State section, while core still
owns persistence, diffing, and model-visible fragment types.
This lets skills stay in the skills extension instead of moving their
runtime into core.
## Shape
```text
extension-owned state
|
| contribute section id + JSON snapshot + renderer
v
core World State
|
| compare with the previous snapshot
v
no message, or one incremental model-visible update
```
The extension API is deliberately small:
```rust
fn contribute_world_state(...) -> Vec<WorldStateSectionContribution>
```
Core adapts the rendered result to `ContextualUserFragment`, records the
snapshot, and keeps the existing compaction/resume behavior.
## What changes
- Adds extension-owned World State section contributions.
- Calls those contributors from the existing per-step World State
builder.
- Restores durable selected capability roots into extension thread state
on resume.
- Keeps the actual model-context fragment and rollout machinery in core.
## What does not change
- No skill or MCP implementation moves out of its extension.
- No new file watcher, generation, or RPC.
- No generic migration of existing World State sections.
- No change to the stable environment-ID assumption from #29856.
## Example
```text
step 1 snapshot: skills = []
step 2 snapshot: skills = [executor-demo:deploy]
core asks the skills extension to render only that change.
```
## Stack
1. **This PR:** let extensions contribute World State sections.
2. Project executor skills through the skills extension.
3. Pin one MCP runtime to each model step.
4. Project selected MCP/app/connector metadata by environment
availability.
5. One end-to-end integration scenario.
## Summary
- keep context injection on a single ContextContributor trait
- split context injection into thread-scoped and turn-scoped
contribution methods
- wire turn-scoped fragments into initial context assembly so extensions
can contribute context from turn-local state
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.
## 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.
## 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
`selectedCapabilityRoots` belongs to one thread, but MCP contributors
previously received only the global Codex config. That left no clean way
for a selected executor capability to contribute MCP servers to its own
thread.
## What this PR does
- Gives MCP contributors a small context containing the config and, for
a running thread, its frozen host-seeded inputs.
- Uses the same thread inputs during startup, status queries, refreshes,
and skill dependency checks.
- Keeps threadless MCP operations and the existing hosted Apps behavior
unchanged.
- Adds coverage showing that two threads resolve independent
registrations and that later lifecycle mutations do not change the
frozen MCP inputs.
This PR does not discover plugin manifests, add MCP servers, or launch
anything new. It only establishes the thread-scoped registration
boundary.
## Follow-ups
- Resolve selected executor plugin roots through their owning
environment filesystem.
- Convert their stdio MCP declarations into environment-bound
registrations and add an executor MCP end-to-end test.
## Verification
- `just fmt`
- `cargo check --tests -p codex-protocol -p codex-extension-api -p
codex-mcp-extension -p codex-core -p codex-app-server`
Tests and Clippy were not run.
## Why
Image generation used `std::fs::read` for referenced image paths, which
did not support environment-backed filesystems or their sandbox context.
## What changed
- Expose optional turn environments to extension tool calls.
- Include each environment’s ID, working directory, filesystem, and
sandbox context.
- Read referenced images through the selected environment filesystem.
- Keep sandbox usage at the extension call site so extensions can choose
the appropriate access mode.
- Consolidate image request construction into one async function.
- Add coverage for successful environment reads and read failures.
## Validation
- `cargo check -p codex-image-generation-extension --tests`
- `just fmt`
- `just bazel-lock-update`
- `just bazel-lock-check`
`just test -p codex-image-generation-extension` could not complete
because the build exhausted available disk space.