## Description
This PR moves review-mode markers onto canonical `TurnItem` lifecycle:
- `TurnItem::EnteredReviewMode`
- `TurnItem::ExitedReviewMode`
Core now emits `ItemStarted` / `ItemCompleted` for both. The completed
items map back into the existing `EnteredReviewMode` /
`ExitedReviewMode` events, so raw core event consumers and legacy
rollout persistence keep seeing the old events.
This is the compatibility layer needed before paginated rollouts persist
review markers as `ItemCompleted(TurnItem)`.
## Why
Review markers were one of the remaining app-server thread items created
directly from legacy events. Giving them canonical items lets paginated
history persist stable turn/item IDs without changing legacy rollouts.
## What changed
- Added canonical review-mode `TurnItem`s and switched review flow to
emit their lifecycle.
- Added completed-item → legacy review event mappings with stable
turn/item IDs.
- Switched app-server live notifications to the generic canonical item
path and kept legacy replay compatible with old payloads.
- Updated `ThreadHistoryBuilder` to replay canonical review items even
though review turns still do not emit `TurnStarted`.
## Description
This PR adds a `codex-extension-items` crate for extension-owned
`TurnItem` schemas, and updates standalone image generation to start
using it via `TurnItem::Extension`.
This gives us a way to prevent Core from having to be aware of all
extension items. App-server still exposes the existing public
`ThreadItem::ImageGeneration` shape, now by wrapping the same shared
`image_generation::ImageGenerationItem` type.
The new `codex-extension-items` crate is necessary because the image gen
extension item is used by:
- `codex-image-generation-extension`, which produces it.
- `codex-tools / core`, which carry it generically.
- `codex-protocol`, which serializes it into lifecycle events and
rollouts.
- `app-server protocol`, which wraps it in public
`ThreadItem::ImageGeneration`
```
extension implementation
↓
codex-extension-items
↓
protocol / tools / app-server
```
We keep the hosted Responses API image generation as
`TurnItem::ImageGeneration` because core still owns its persistence and
legacy fanout.
### Before
Standalone image generation is implemented as an extension, but its item
representation previously lived in the core protocol. This sets the
precedent that core is aware of all extension items, which would be good
to avoid.
```
image-gen extension
→ constructs codex_protocol::ImageGenerationItem
→ emits ExtensionTurnItem::ImageGeneration
→ core matches ImageGeneration specially
→ protocol stores TurnItem::ImageGeneration
```
### After
```
image-gen extension
→ constructs extension-owned ImageGenerationItem
→ emits generic ExtensionItem
→ core transports/persists it generically
→ app-server wraps ImageGenerationItem as ThreadItem::ImageGeneration
```
Future extension items can have typed app-server APIs without adding a
new `TurnItem` variant, `ExtensionTurnItem` variant, or core emitter
match arm.
## What changed
- Added `codex-extension-items` with the closed `ExtensionItem` enum and
shared `image_generation::ImageGenerationItem` schema.
- Added generic `TurnItem::Extension(ExtensionItem)` and
`ExtensionTurnItem::Extension { item, legacy_events }` paths.
- Updated standalone image generation to emit a typed extension item and
provide its existing legacy `ImageGenerationBegin` /
`ImageGenerationEnd` events explicitly.
- Kept canonical lifecycle ordering: core emits `ItemStarted` /
`ItemCompleted` before extension-provided legacy events.
## Follow-up
Standalone web search still uses its typed special-case path. Migrating
it later would let `ExtensionTurnItem` collapse into a single
extension-item struct.
## Description
This PR adds legacy `EventMsg` mappings for the `TurnItem` types
introduced in [#30282](https://github.com/openai/codex/pull/30282):
- `CommandExecution`
- `DynamicToolCall`
- `CollabAgentToolCall`
- `SubAgentActivity`
When their producers move to canonical `ItemStarted` / `ItemCompleted`,
raw core event consumers can still receive the existing begin/end-style
events. The canonical item lifecycle remains the live source of truth.
We also record the mapped legacy events in rollout trace so the producer
migration preserves the existing tool-runtime trace entries.
## Why
This is the compatibility layer for the follow-up producer migrations.
Splitting it out first keeps each producer PR small and keeps the legacy
mapping in one place.
## What changed
- Added `TurnItem` → legacy `EventMsg` mappings in
`protocol/src/legacy_events.rs`.
- Added the command execution status conversion used by the exec
mapping.
- Added focused coverage for command execution and dynamic tool
mappings.
## Stack
1. [#30956](https://github.com/openai/codex/pull/30956) — isolate legacy
item fanout ← **this PR**
2. [#30283](https://github.com/openai/codex/pull/30283) — emit canonical
`TurnItem` lifecycle
3. [#30188](https://github.com/openai/codex/pull/30188) — persist
canonical items for paginated threads
## Description
Move legacy `EventMsg` projection code out of the canonical item and
protocol schema modules into `protocol/src/legacy_events.rs`.
This is a behavior-neutral extraction. It keeps the existing
`HasLegacyEvent` API and the existing legacy projections unchanged,
while giving compatibility fanout a single home.
## Why
Canonical `TurnItem` types and wire event schemas should not own the
implementation details for legacy compatibility projections. Isolating
that code makes the boundary explicit and keeps follow-up canonical
lifecycle work easier to review.