## What changed
- Replace accepted-line fingerprint generation with diff-based added and deleted line counts.
- Keep `line_fingerprints` in the analytics payload as a statically empty field for schema compatibility.
- Remove the fingerprint types and public parsing API that are no longer used.
## Testing
- Update diff parsing tests to cover accepted-line counts without generating fingerprints.
GitOrigin-RevId: 4247c76c7b56b1a323f70f960823937352b84f7b
## Why
Unified exec can yield while a command is still running. Plugin measurement
collection must remain active until that background command exits, including
when its item completion arrives after the turn has completed.
## What changed
- Keep the plugin metrics sidecar with the stored process and let either the
exit watcher or a poll that observes completion finalize it exactly once.
- Retain completed turn analytics state while tool items are pending so late
command completion events can be emitted without duplicating the turn event.
## Testing
- Verify that a background command completed after its turn emits a command
execution event and does not emit a second turn event.
GitOrigin-RevId: ecf715b3e047aa29ca9a417d12d955257fed8557
## What changed
- Add `PluginMeasurementsInput` and `PluginMeasurementRow`, plus an analytics
client method for recording batches and checking whether analytics are enabled.
- Emit each valid row as a `codex_plugin_measurement_event` with thread, turn,
item, plugin, execution, and operation context.
- Bound batches at 100 rows and reject non-finite values, invalid identifiers,
and rows with more than eight dimensions.
- Treat measurement events as plugin events when routing API-key analytics.
## Testing
- Cover batch limits, row filtering, serialized event payloads, and API-key
plugin-event routing.
GitOrigin-RevId: f949ed06246ee1e22466df2660076f2d4f556444
## Why
Subagent-start facts can arrive before their parent's connection context is
recorded, leaving later analytics events without client metadata.
## What changed
- Resolve a thread's connection by walking its parent-thread chain when the
connection was not captured directly.
- Use the resolved connection for subagent setup, tool-call analytics, and
completed turn events.
## Testing
Extend the subagent analytics test to start the child before the parent context
arrives and verify both command-execution and dynamic-tool-call events.
GitOrigin-RevId: c4d74a22de34abd6c6c5df1717a49ae0e1a9445d
## What changed
- Emit skill invocation analytics when a resource-backed skill is explicitly selected or its main resource is successfully read from the first page.
- Preserve provider-supplied skill IDs and user scope when available, and derive a stable ID from the main resource otherwise.
- Deduplicate repeated implicit reads of the same main resource within a turn.
## Testing
- Extend the orchestrator skill integration test to cover explicit and implicit invocation events, stable fallback IDs, failed reads, and repeated-read deduplication.
GitOrigin-RevId: 7d8453b31b5a2205847e65cda2c46183d6c97b45
## What changed
- Recognize validated create and edit marker commands from the trusted presentations, documents, spreadsheets, and PDF plugins.
- Emit a `codex_artifact_operation` analytics event plus started-count and expected-output-count metrics when a recognized marker starts through unified exec.
- Attribute remote executor commands only when their plugin cache path and script contents match a trusted local plugin, including primary-runtime plugins.
## Testing
- Cover supported marker combinations and reject mismatched plugins, paths, arguments, counts, and output formats.
- Cover local, relocated remote, and Windows-style plugin attribution, including rejection of modified remote scripts.
GitOrigin-RevId: 475cc1689aa7f371a24dec0a8bdb6f7fd5a2e922
## What changed
- Add structured `usageLimitExceeded` metadata to failed image-generation
items, including the image limit ID and optional reset timestamp.
- Preserve the failure metadata in completion events, app-server thread
history, thread reads and resumes, and migrated legacy rollouts.
- Export the new failure type in the generated JSON and TypeScript schemas.
## Testing
- Cover usage-limit responses through item completion, persistence, and thread
resume.
- Verify legacy rollout migration retains image-generation failure metadata.
GitOrigin-RevId: 91c31a2a47456b6b916ee03002ff314b05946dd4
## What changed
- Emit `codex_thread_archive_event` when a thread is successfully archived or
unarchived, including the thread ID, action, and producer timestamp.
- Route archive notifications through the analytics client while preserving
normal app-server notification delivery.
## Testing
- Extend app-server coverage for archive and unarchive analytics, including
descendant archives and a repeated archive request that fails without
producing another event.
GitOrigin-RevId: 0d973c232fd795b7cd6cddd47ecc9446234c44dd
## What changed
- Add `transparentBackground` to image-generation items and legacy completion events.
- Map Images API backgrounds to `true` for transparent, `false` for opaque, and `null` for automatic or unavailable values.
- Preserve the metadata in live notifications and persisted or resumed thread history while continuing to deserialize older items that omit it.
## Testing
- Cover transparent, opaque, and automatic backgrounds, including image edits and thread read/resume flows.
- Verify the stable JSON shape and optional TypeScript field.
GitOrigin-RevId: 08527f37f23431df45fab1ed34eb82b2139b49d7
## What changed
- Track Agent Plugin manifests through plugin, skill, and MCP loading so their capabilities use format-specific behavior without changing legacy plugins.
- Discover only direct-child skills, exclude app and hook capabilities, isolate MCP data, and reject MCP configuration files that are non-regular or resolve outside the plugin root.
- Bound model-visible skill instructions, plugin instructions, MCP descriptions, schemas, individual tools, and the aggregate Agent Plugin MCP tool set.
- Stop MCP and OAuth redirects when Agent Plugins send configured or authorization headers, while retaining existing redirect behavior for legacy MCP servers.
## Testing
- Add coverage for capability filtering, skill discovery, isolated MCP data and reserved-path expansion, unsafe MCP configuration files, context limits, and redirect handling.
GitOrigin-RevId: c9af66b051269f3226628ca280a58d32c808c38f
## What changed
- Remove the hidden `PairProgramming` and `Execute` variants from `ModeKind`.
- Delete their unused prompt templates and simplify mode handling to cover only `Default` and `Plan`.
GitOrigin-RevId: 8d24e3f562a09e3071b2decae275d2b3871a1e9c
## Why
App-server clients need to advertise structured MCP extension settings, including supported MCP App UI MIME types, rather than only opting into OpenAI form elicitation with a boolean.
## What changed
- Add an `extensions` map to initialize capabilities and preserve `mcpServerOpenaiFormElicitation` as a legacy alias for `openai/form`.
- Capture the declared extension profile when a thread is started, resumed, or forked, propagate it to subagents, and advertise it to downstream MCP servers during initialization.
- Keep the profile stable for the lifetime of the loaded session instead of changing it on later turns or direct tool calls.
## Testing
- Cover extension conversion, downstream MCP initialization, session isolation, legacy form support, and subagent inheritance.
GitOrigin-RevId: fbcedbb74ce788e574b0f884a4c45c4cedb9de54
## What changed
- Emit dynamic tool-call analytics for code mode `exec` and `wait` calls, including duration and terminal status.
- Associate code-mode cells and their nested tool calls with the originating and subsequent model response IDs.
- Buffer correlated tool events until the next response is known, while flushing pending events when turns, threads, or the analytics queue close.
## Testing
- Add reducer coverage for correlating `exec`, `wait`, and nested tool events across turns.
- Add an app-server test that verifies production analytics include the cell and response IDs for a code-mode `exec` call.
GitOrigin-RevId: 8b34ab59c1ad68ebbd57f9c8a05f19ab04df48a4
## What changed
- Parse `codex_rollout_budget_units` from completed Responses API usage into
`TokenUsage`.
- Keep the provider-only value out of serialized protocol, JSON schema, and
TypeScript representations.
## Testing
- Cover the value in unit and end-to-end SSE response parsing tests.
GitOrigin-RevId: a53d4202beb9c8985d25894cb10e7c01f20b2a44
## What changed
- Record the effective detail setting and source and prepared dimensions for
successfully decoded images.
- Associate message images with their role and tool-output images with their
originating call ID.
- Include the collected image preparation metadata in the corresponding turn
analytics event.
## Testing
- Cover image resize metadata, tool-output attribution, turn aggregation, event
serialization, and app-server analytics output.
GitOrigin-RevId: dbfb5ba1ce7cb02dd93d9b579c5109817f16aef5
## Why
Read command actions were omitted when a selected environment used a path
convention foreign to the app-server host. Clients need these actions to refer
to files in the executor's filesystem, not paths interpreted on the host.
## What changed
- Resolve read paths against the executor's `PathUri` and serialize them using
the executor's native POSIX, Windows drive, or UNC convention.
- Accept same-drive Windows-relative paths while continuing to reject paths
that require another drive's executor-local current directory.
- Document the path convention used by `commandExecution` notifications.
## Testing
Cover native and cross-platform path serialization, Windows drive-relative
joins, and selected-environment `item/started` and `item/completed`
notifications.
GitOrigin-RevId: 3c5a9bb1caafd35e04a0a0cceab91fee590372b9
## What changed
- Send the active turn ID in the `x-codex-image-turn-id` header for image generation and edit requests.
- Add `explicit_client_interrupt_requested_at_ms` to turn analytics. Record the earliest explicit interrupt request that receives a successful response, while excluding rejected requests.
- Track errors returned by `turn/interrupt` so pending analytics state is cleaned up correctly.
## Testing
- Cover turn ID headers for image generation and editing.
- Cover accepted, retried, and rejected explicit interrupt requests in analytics tests.
GitOrigin-RevId: 80fe996a161b4952b79c5c38238f75b7661b92af
## What changed
- Propagate MCP tool `readOnlyHint` annotations through tool-call start and
completion events.
- Include the optional hint in persisted thread history and app-server
`mcpToolCall` items, preserving compatibility when the annotation is absent.
- Document that the hint describes tool capability, not the outcome of a
particular invocation.
## Testing
- Cover read-only and write-capable tools in live events and persisted rollout
data.
- Verify the hint survives thread reads and resumes for both in-progress and
completed MCP calls.
GitOrigin-RevId: dddfe905146075e5137a1094da485b86b99807f0
## What changed
- Pass server notifications to analytics tracking by reference.
- Clone only the notification variants that are recorded as analytics facts.
## Testing
- Add unit coverage that verifies a turn diff notification is enqueued and a command output delta notification is ignored.
GitOrigin-RevId: 2fa0711dbdcca33dce82c3ef78ee53a3f5a65bd9
## What changed
- Add `thread/section/move` to atomically move a thread into, within, or out of a section. Threads can be inserted before an existing member or appended, and moves within a section preserve `sectionEnteredAt`.
- Add `section_position` sorting to `thread/list`, with ascending order as its default, and expose `sectionEnteredAt` in thread responses.
- Persist section positions and entry times in SQLite, including migration of existing section members into recency order. Section membership is no longer updated through `thread/metadata/update`.
## Testing
- Cover section moves, reordering, pagination, persistence across restarts and rollout reconciliation, concurrent updates, and rank renumbering.
GitOrigin-RevId: aec6d7ddedca5277029b5caf5c074975397e956c
## What changed
- Keep `ClientResponsePayload` typed through the app-server outgoing queue and serialize it at the transport boundary, avoiding an intermediate `serde_json::Value` while preserving the JSON-RPC wire shape.
- Return a JSON-RPC internal error when a response cannot be serialized, including over remote control, and skip unserializable analytics responses.
- Scan reverse JSONL input a chunk at a time with a larger buffer instead of processing each byte individually.
## Testing
- Cover typed response serialization and serialization failures for direct and remote-control transports.
- Cover filtering unserializable responses from analytics.
GitOrigin-RevId: cc4fd7a43461a273315bb95261bd1ae3ff65fbf7
## What changed
- Replace the `isPinned` thread metadata and filters with an optional persisted
`section` and `sectionId`.
- Add the paginated `threadSection/list` app-server method so clients can
discover sections even when they contain no threads.
- Seed a stable `Pinned` section, validate section assignments, and support
filtering for a specific section or for unsectioned threads.
## Testing
- Cover section protocol serialization, listing and pagination, metadata
updates, filtering, persistence, migration compatibility, and operation
without SQLite state.
GitOrigin-RevId: 7972b5471d29317b9387bfd90aa9f573f691ad4c
## What changed
- Add `remote_plugin_id` to skill invocation facts and analytics event parameters.
- Propagate the ID for both explicit and implicit plugin skill invocations.
## Testing
- Cover explicit and implicit remote plugin skill invocations with analytics integration tests.
GitOrigin-RevId: 3e1d25b5ff88dcff9c2b6c9fe44bca97e8778221
## What changed
- Resolve shell and unified-exec commands against the trusted plugin roots loaded for each turn.
- Add optional `pluginId` and safe plugin-relative `scriptPath` fields to command execution items and legacy execution events, and propagate them through app-server notifications.
- Include the attribution in command execution analytics while rejecting absolute, unsafe, and unattributed script paths.
## Testing
- Cover attribution for cached curated and remote plugin scripts from command execution through core and app-server events.
- Verify analytics serialization and unsafe-path filtering.
GitOrigin-RevId: 02fac3a233284ccfc6642fa502a95f1881dba83d
## What changed
- Wrap `CodexErrorDetails` and an optional retry delay in `CodexErr`, allowing any mapped error to preserve server-provided retry timing.
- Generate the payload-free `CodexErrKind` classification alongside the error details and reuse it for analytics.
- Update error handling sites to inspect `CodexErr::details()` while preserving existing display, debug, protocol mapping, and retryability behavior.
## Testing
- Add coverage for legacy debug formatting, error-specific retryability, and retry-delay propagation through API error mapping.
GitOrigin-RevId: d3ab8a305f2a2ee21d0c0a8c8c388b06dda9c59a
## What changed
- Add `isPinned` to thread responses and allow `thread/metadata/update` to pin or unpin stored threads.
- Add an `isPinned` filter to `thread/list`, including cursor-based pagination and combinations with relationship filters.
- Persist pin state in SQLite without modifying rollout files, default existing threads to unpinned, and preserve pins through reconciliation and archive transitions.
## Testing
- Cover protocol serialization, database migration and indexing, pin updates, filtered pagination, rollout reconciliation, and archive/unarchive behavior.
GitOrigin-RevId: c2eacabd6dae465e2ae6ce8a169e00740960cdb7
## What changed
- Add `compaction_ms` to turn profile facts and turn analytics events.
- Measure manual and automatic compaction as an exclusive profile phase instead of including it in surrounding idle time.
- Complete the profile and total turn duration from the same instant so their elapsed times remain consistent.
## Testing
- Cover compaction phase accounting and profile-duration consistency.
- Update turn event serialization coverage for `compaction_ms`.
GitOrigin-RevId: 50f0e1cf19789a807f9f56c2ef763c620a5d641c
## Why
The in-process app server can shut down while analytics for a completed turn are
still queued, preventing the completed-turn and accepted-line events from being
delivered.
## What changed
- Add a bounded analytics queue flush that waits for all preceding facts to be
processed.
- Flush analytics after the in-process runtime drains its outbound work and
before acknowledging shutdown.
- Extend the in-process shutdown timeout budgets to cover runtime draining and
the best-effort analytics flush.
## Testing
- Verify that the flush waits for preceding facts and is a no-op when analytics
is disabled.
- Verify that shutdown waits for delayed analytics delivery, including the
completed-turn and accepted-line events produced by a file change.
GitOrigin-RevId: 57ca24e535e5e2f3f3202b2e5498f075676c910a
## What changed
- Add an optional opaque `providerId` to `externalAgentConfig/import` for analytics attribution, independently of the `migrationSource` selector.
- Include the provider ID in completed-import and import-failure analytics events.
- Have TUI imports identify their selected migration source as the provider.
## Testing
- Cover provider attribution in app-server import completion and failure tests and analytics serialization tests.
GitOrigin-RevId: 0ee7313a43652e5305939c526bf6b903a79334ad
## What changed
- Add the `SessionEnd` hook event to hook configuration, protocol schemas, analytics, and the hooks browser.
- Run matching hooks for root threads during shutdown, including app-server archive, delete, idle unload, and graceful shutdown. Flush the transcript first and provide the session ID, transcript path, working directory, and `reason: "other"`.
- Keep teardown bounded: hook output is advisory, the default timeout is one second, configured timeouts are capped at three seconds, and async hooks run synchronously with a warning.
## Testing
- Cover transcript availability, matcher selection, timeout normalization, ignored control output, subagent exclusion, archive/delete ordering, and graceful shutdown of multiple loaded threads.
GitOrigin-RevId: 5d19a658677a137caf836ed5042dcb43f5eb6d1a
## Why
Multi-agent V2 spawned threads are controlled by their parent and reject direct
app-server input. The TUI should expose that ownership instead of presenting an
input flow that the server will reject.
## What changed
- Add the experimental `Thread.canAcceptDirectInput` capability for loaded
threads and populate it from the same policy used to validate `turn/start`
and `turn/steer`. Stored, unloaded threads report `null`.
- Carry the capability through TUI thread discovery, selection, startup, and
resume flows, with a compatibility fallback for older app servers.
- Present parent-owned threads as view-only, preserve drafts and queued input,
block agent-directed commands and settings shortcuts, and keep local
navigation and inspection commands available.
## Testing
- Cover V1 writable and V2 view-only spawned threads through selection and
resume.
- Cover composer submission paths, queued and initial input preservation,
allowed navigation commands, settings shortcuts, and the view-only UI.
GitOrigin-RevId: 495ad051249e5577fb0b0c1cc8c40a0998d04918
## What changed
- Remove `template_id` from MCP tool call items and lifecycle events.
- Remove `templateId` from app-server `McpToolCallAppContext` responses and generated schemas.
- Update protocol conversions, documentation, and existing test expectations for the revised metadata shape.
GitOrigin-RevId: f2b1304eadff661eec3966673ea71206b1542b98
## What changed
- Parse `cache_write_tokens` from response input token details and carry it through token usage aggregation.
- Expose `cache_write_input_tokens` in protocol, app-server, exec, and TypeScript SDK usage events, defaulting it to zero for compatibility with older payloads.
- Report cache-write usage in analytics, metrics, traces, and rollout data alongside existing cache-read usage.
## Testing
- Add coverage for response parsing, usage event output, aggregation, analytics, metrics, and telemetry fields.
GitOrigin-RevId: 634e4fbe3086a8923c0ee13b622573e7d9d221bd
## What changed
- Allow `thread/start` to select experimental `historyMode: "paginated"` when the thread store supports turn and item pagination.
- Resume paginated threads with bounded model context and require `excludeTurns: true` instead of loading full history.
- Return `turnsBackwardsCursor` and `itemsBackwardsCursor` from `thread/resume` so clients can hydrate durable history through `thread/turns/list` and `thread/items/list` while newer records arrive through live notifications.
- Reject operations that still require full history, including `initialTurnsPage`, `thread/read` with `includeTurns: true`, `thread/rollback`, and detached review.
- Make item cursors independent of the optional `turnId` filter.
## Testing
- Cover paginated thread start, cold and live resume, backwards cursor hydration, metadata reads, unsupported operations, and stores without pagination support.
GitOrigin-RevId: c42f9b268e29d049843330f474fa3ecde12f5b8a
## What changed
- Add `subErrorType` to external agent config import failures and carry it through progress and completion notifications, persisted import histories, and analytics.
- Expose plugin store failure subtypes from `PluginInstallError` so manual installs and imported plugins report details such as `failed_to_copy_plugin_file` while failures without a subtype remain `null`.
- Keep existing persisted import records compatible by defaulting the new field when it is absent.
## Testing
- Cover store I/O subtype extraction, analytics serialization, persisted import histories, and migration notification fixtures.
GitOrigin-RevId: ec1a85623fc826cbd7cfe9f55c2dd2bc86665937
## What changed
- Define `SleepItem` in `codex-extension-items` and carry `clock.sleep` events through `TurnItem::Extension`.
- Preserve sleep persistence, thread-history reconstruction, and the app-server's `type: "sleep"` schema while reusing the extension-owned type.
## Testing
- Add serialization coverage for the `clock.sleep` extension wire shape and its protocol wrapper.
- Update sleep lifecycle and thread-history tests to exercise the extension-owned item.
GitOrigin-RevId: 34bb33ed9b90a09bf4744aafdb56e019c717a168
## What changed
- Add `codex-agent-extension` with an `AgentRunner` that starts a resolved agent prompt in a thread forked from its parent.
- Propagate the invocation's trace context, select the configured execution environments, submit the initial prompt, and return the spawned thread and turn identifiers.
- Reject empty agent prompts and report when the owning thread manager is no longer available.
## Testing
- Add an integration test that verifies the agent runs in a forked thread, returns the started turn identifier, completes the turn, and sends the resolved prompt to the model.
GitOrigin-RevId: ffb805efabadf758969d6611a6867db6fa9f059a
## What changed
Allow API key sessions to send plugin-attributed `codex_plugin_used`,
`skill_invocation`, and `codex_mcp_tool_call_event` events. Continue to drop
events without a plugin ID and all other analytics events for these sessions.
## Testing
Add coverage that mixes plugin and non-plugin events and verifies that only the
three plugin-attributed event types are delivered without server-owned auth
fields.
GitOrigin-RevId: 1f47af5e9516723154f566ad032dba3ebd98abf9
## What changed
- Add `session_id` to the shared tool item event payload, sourced from the
thread metadata.
- Preserve the parent session ID on tool events emitted by subagent threads.
## Testing
- Cover serialization of the session ID for command execution events,
subagent tool events, and every supported completed tool item type.
GitOrigin-RevId: 73afc0e1be1ddec4fb5a42f43a23f1e87360e68f
## Why
Standalone web search can return structured result DTOs separately from its
model-facing text output. App-server clients need access to that data without
coupling Codex to every result type or field.
## What changed
- Preserve the optional `results` array from `/v1/alpha/search` as opaque JSON
through web-search completion events, thread history, and app-server
`webSearch` items.
- Keep responses from older endpoints compatible when `results` is absent, and
allow clients to ignore result types and fields they do not understand.
- Regenerate the protocol schemas and TypeScript definitions for the optional
field.
## Testing
- Cover responses with missing, empty, and forward-compatible result payloads.
- Verify standalone web-search results round-trip through app-server item
notifications and thread-item conversion.
GitOrigin-RevId: 739ef5694ece5733f7b8749ec41d6bfa3f33c8c5
## What changed
- Add the optional `connector_id` to `codex_mcp_tool_call_event` parameters.
- Populate it from the MCP tool call's `app_context` when available.
## Testing
- Extend the completed tool item analytics test to verify the connector ID is serialized.
GitOrigin-RevId: ea62202318dd492fa0ffd4ff1d48a66d1cc37e96
## Summary
- discover enabled-plugin marketplace sources from the user-level known
marketplace registry
- preserve scoped settings and supported Git/directory declarations,
falling back to the matching registry `installLocation` for file, URL,
npm-hosted, and inline marketplace sources
- resolve relative registry materialization paths against the
external-agent home
- reuse the existing marketplace add and plugin install flow during
import
- emit `plugin_not_found` tracing and analytics with plugin and
marketplace identity
## Why
Enabled plugins can remain in settings after their marketplace source
has moved into the user-level registry. Import detection previously
skipped those plugins because it only consulted settings-defined
marketplace sources.
## Impact
`/import` can now restore those marketplaces and install their enabled
plugins through the normal plugin installer, including npm, Git, and
local plugin sources. Missing plugins remain visible in import results
and the plugin-install failure analytics stream.
## Description
This PR migrates standalone web search onto the extension-owned
turn-item path introduced in #31283.
Standalone web search now emits `ExtensionItem::WebSearch` through
generic `TurnItem::Extension`, while app-server still exposes the
existing typed `ThreadItem::WebSearch` JSON shape. Hosted Responses API
web search stays on core-owned `TurnItem::WebSearch`.
## What changed
- Added `web_search::WebSearchItem` and `WebSearchAction` to
`codex-extension-items` under the stable `web.search` kind.
- Collapsed `ExtensionTurnItem` to generic `{ item, legacy_events }` now
that no typed extension special cases remain.
- Kept the existing `WebSearchBegin` / `WebSearchEnd` compatibility
events and canonical-first ordering.
- Updated app-server projection/history and generated TypeScript; the
app-server JSON schema is unchanged.
## 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 a new `historyMode = "legacy" | "paginated"` to `Thread`.
This will be stored in `SessionMeta` in the JSONL rollout file and as a
new column in the SQLite thread_metadata table, and exposed on
`thread/start` and on the `Thread` object in app-server.
## What changed
- Added canonical `ThreadHistoryMode` with `legacy` and `paginated`,
defaulting old and new SessionMeta to `legacy`.
- Carried `history_mode` through core session config, ThreadStore stored
metadata, local/in-memory stores, rollout metadata extraction, and the
existing SQLite `threads` table.
- Added experimental `historyMode` to app-server v2 `Thread` and
`thread/start`.
- Made paginated stored threads metadata-discoverable but unsupported
for legacy full-history reads, `load_history`, live resume, and create
paths.
- Regenerated app-server schema fixtures and added
protocol/state/thread-store/app-server coverage for persistence and
fail-closed behavior.
## Compatibility floor
Because users may be running various versions of Codex binaries on the
same machine (TUI, Codex App, etc.), we will need to establish a
compatibility floor for upcoming paginated threads, which will change
how thread storage reads and writes work.
The overall plan here:
```
Release N:
- Add historyMode to SessionMeta / Thread / SQLite metadata.
- Teach binaries to understand paginated threads.
- If a binary sees `historyMode="paginated"` but does not support the paginated contract, it refuses to resume/mutate the thread.
- Default remains `"legacy"`.
Release N+1:
- First-party clients start opting into paginated threads where appropriate.
- Internal dogfood / staged rollout.
- Measure old-client usage and paginated-thread unsupported errors.
Release N+2:
- Only after Release N+ is overwhelmingly deployed, make paginated the default.
- Accept that a small tail of N-1-or-older binaries may not understand paginated threads.
```
The important behavior change is fail-closed handling for a binary that
encounters a persisted `paginated` thread before it knows how to fully
support paginated history. In app-server, if a thread is `paginated`, we
will:
- allow metadata-only discovery paths like `thread/list` and
`thread/read(includeTurns=false)`, so clients can still see the thread
and inspect its `historyMode`
- reject legacy full-history/live-thread paths like
`thread/read(includeTurns=true)` and `thread/resume` with an unsupported
JSON-RPC error
- avoid silently treating an unknown or future `historyMode` as `legacy`
Under the hood, the ThreadStore layer also rejects legacy operations
that would need to load or replay the full thread history for a
paginated thread. That gives us the behavior we want for Release N:
future paginated threads are visible, but this binary fails closed
instead of trying to operate on them as if they were legacy threads.
## Why
Desktop Work threads and regular Codex threads can share the same
app-server connection. App-server analytics currently copy
`product_client_id` from connection metadata for every thread-scoped
event, so Work thread activity is attributed to the Desktop connection
instead of the thread's resolved originator. This prevents analytics
from distinguishing the two products on a shared connection.
## What changed
- Publish the resolved originator after a thread is materialized,
covering new, resumed, forked, and subagent threads.
- Store that originator in the analytics reducer's existing per-thread
state.
- Override only `app_server_client.product_client_id` for thread, turn,
tool, review, goal, guardian, and compaction events while preserving the
connection's client name, version, and transport metadata.
- Fall back to the connection-wide product client ID when a thread has
no originator override.
- Preserve persisted originators in thread initialization analytics for
resume and fork flows.
## Validation
- `just test -p codex-analytics
thread_originator_overrides_shared_connection_across_thread_events
subagent_events_keep_thread_originator_with_explicit_turn_connection`
- `just test -p codex-app-server
turn_start_tracks_thread_originator_in_analytics
thread_start_tracks_thread_initialized_analytics
thread_fork_tracks_thread_initialized_analytics
thread_resume_tracks_thread_initialized_analytics`
- `just test -p codex-core thread_manager`
Summary
- Emit `codex_plugin_install_requested` when a validated plugin install
request is made, before the user accepts or declines the elicitation.
- Record the exact model-visible plugin ID, remote plugin ID, required
connector IDs, stable suggestion ID, and `endpoint_recommendation` vs
`legacy_discovery` source.
- Keep `suggest_reason` out of telemetry and leave connector-only
install requests unchanged.
Rollout
- Backend/schema dependency:
https://github.com/openai/openai/pull/1065270
- Land the backend PR before this producer starts sending the event.
Validation
- `just test -p codex-analytics` (83 passed)
- `just test -p codex-core request_plugin_install` (17 passed)
- `just fix -p codex-analytics`
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`
## Why
Work(TPP) threads can be launched from the Desktop app, but if they all
keep the Desktop app's default originator then downstream attribution
cannot distinguish local Work launches from cloud-backed Work launches.
`thread/start.serviceName` already carries that launch signal, while
`SessionMeta.originator` is the durable thread-level value that survives
resume and fork.
This change converts the Desktop Work service names into an effective
originator at thread creation time, persists that originator with the
thread, and keeps using it for later model requests and memory writes.
## What changed
- Map `CODEX_WORK_LOCAL` and `CODEX_WORK_CLOUD` service names to
per-thread originators, while preserving
`CODEX_INTERNAL_ORIGINATOR_OVERRIDE` as the highest-precedence override.
- Persist the effective originator in `SessionMeta.originator`, read it
back on resume/fork, and inherit the parent originator for subagent
spawns when there is no persisted session metadata.
- Handle truncated `SpawnAgentForkMode::LastNTurns` forks by falling
back to the live parent originator when the forked history no longer
includes `SessionMeta`.
- Thread the per-thread originator through Responses headers,
websocket/compaction request paths, thread-store creation, rollout
metadata, and memory stage-one telemetry.
## Verification
- `just test -p codex-core
agent::control::tests::spawn_thread_subagent_inherits_parent_originator_without_fork
agent::control::tests::spawn_thread_subagent_fork_last_n_turns_inherits_parent_originator_without_session_meta
thread_manager::tests::originator_override_precedes_service_name_remapping`
- `just test -p codex-core
agent::control::tests::resume_thread_subagent_restores_stored_metadata_and_effective_multi_agent_mode`
- `just test -p codex-memories-write`
- `just fix -p codex-core -p codex-memories-write`
- `git diff --check`