## Why
Reverse search fetched persistent history one entry at a time, making a deep or unsuccessful search require a lookup for every older entry.
## What changed
- Keep the newest-entry probe lightweight, then scan older persistent history in bounded batches.
- Cache batch results by absolute offset and preserve continuation cursors across repeated searches.
- Ignore stale responses, retry failed batch reads a bounded number of times, and restore the draft without reporting a false “no match” state when history is unavailable.
## Testing
- Cover batch continuation, caching after query changes or cancellation, stale metadata, retry exhaustion, and thread routing.
- Verify that scanning 1,024 entries uses one single-entry lookup and eight batch lookups.
GitOrigin-RevId: efc791fcbd5290050af703dd31c5c21b33240e23
## Why
The Desktop app can use Codex or ChatGPT branding while retaining stable platform identities. CLI discovery and TUI handoff should not depend on a single display name or hardcoded executable path.
## What changed
- On macOS, search for both `ChatGPT.app` and `Codex.app`, and accept only bundles with the `com.openai.codex` identifier.
- On Windows, detect installs by their package app ID and resolve the `codex` protocol executable from the AppX manifest before handing off a TUI session.
- Use “Desktop app” consistently in CLI and TUI user-facing text.
## Testing
Add coverage for selecting a ChatGPT-named Codex bundle and rejecting the classic ChatGPT bundle.
GitOrigin-RevId: b84a56eb6e960712152e1a9b023ab530ec9a354a
## 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
## What changed
Remove the unused `PairProgramming` and `Execute` footer indicator variants and their label and styling branches. Keep `Plan` as the only `CollaborationModeIndicator` rendered by the TUI.
GitOrigin-RevId: 5f3e6c8ff50a04d41dd5fc342cf1347a7e4f78fe
## 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
Give command, permissions, patch, and MCP elicitation approvals dedicated
request structs, and update TUI routing and rendering to use the wrapped
payloads.
GitOrigin-RevId: ffee4058dd27505be2a11de3758ac12bb707b6b8
## Why
Tabs in composer text could render at a different width from the cursor and
wrapping calculations, causing misaligned cursors and unexpected line breaks.
## What changed
- Replace tabs with single-column spaces for wrapping and rendering while
preserving the original editable text and byte ranges.
- Apply the same display representation to base text, styled elements, and
highlighted ranges.
## Testing
Add coverage for cursor alignment, rendered output, and wrapping of text that
contains tabs.
GitOrigin-RevId: 06d8489621d2f8b0823c7a004079d152dd0cab66
## What changed
- Use `$`-mention boundary rules when deciding whether a skill mention followed by a suffix is already bound.
- Fall back to the editable skill token on the left when the token on the right is a bound mention such as `$bound/path` or `$bound.config`.
## Testing
- Add completion-target coverage for path and configuration suffixes.
- Add a composer snapshot covering the skill popup fallback for a bound path suffix.
GitOrigin-RevId: bf72237dfd56cfe6f590a3fab10c2bfe03b088a8
## Why
Skill mentions and shell parameters both use a `$` prefix. Numeric and special
shell parameters should not open or displace completion popups, while loaded
skills can legitimately have digit- or hyphen-leading names.
## What changed
- Classify `$` queries as skill candidates, shell variables, definite shell
parameters, or ambiguous shell-like names.
- Offer completion for an ambiguous name only when it fuzzy-matches a bindable
loaded mention, reusing the filtered catalog when the popup opens.
- Keep definite shell forms such as `$1`, `$12`, `$-`, and `$_` out of mention
completion and preserve completion targets elsewhere in the composer.
## Testing
Added unit and snapshot coverage for positional and special parameters,
digit- and hyphen-leading skills, uppercase shell variables, unbindable
qualified skills, and competing completion targets.
GitOrigin-RevId: 8bb64cb5ca0fa8233c7fadb11a53678d57eb814c
## What changed
- Leave an output-free interrupted prompt in the transcript and open a blank
composer for the user's next instruction.
- Use the normal interruption notice for both `Esc` and `Ctrl-C` interrupts.
- Track safety-buffering prompts separately so failed retries can still restore
the prompt, including after switching between threads.
## Testing
- Cover output-free `Esc` and `Ctrl-C` interrupts, failed safety-buffer retries,
and restoration of safety-buffer state from thread snapshots.
GitOrigin-RevId: 30b368d00dfdb981d06d4c95ce3f252389b7917c
## Why
Special filesystem subpaths can contain relative or foreign-platform path
spellings. Treating this wire text as a host-native path can reinterpret it.
## What changed
- Store special filesystem subpaths as strings in the core permissions model.
- Represent app-server subpaths as `LegacyAppPathString` and convert them at
the protocol boundary without changing their spelling.
- Add `LegacyAppPathString::from_string` for callers that already own legacy
app-server path text.
GitOrigin-RevId: 2214f03aa997fd0e6eb2ab60d425304bb4bd6132
## What changed
- Include the active Codex Apps tools cache and connector directory cache, when available, in diagnostic uploads that include logs.
- Resolve cache paths from the current authentication context and use stable attachment filenames.
- List both optional cache files in the upload consent dialog.
## Testing
- Add coverage for selecting existing active cache files, including a directory cache without an account ID.
- Update consent dialog snapshots to include the optional attachments.
GitOrigin-RevId: 1d4ffbf2bcb96653f03dc25f91d80dd65038d585
## What changed
- Make `Esc` dismiss the slash-command popup immediately without changing the draft or interrupting a running task.
- Keep the popup closed while the first-line command token is unchanged, and reopen it after the token is edited.
## Testing
- Cover dismissal while idle and while a task is running.
- Add a snapshot confirming that the dismissed popup remains hidden.
GitOrigin-RevId: f0ee84af080181e8d92c435f474ded1c8cb5b457
## What changed
- Resolve `@` and `$` completion targets on either side of the cursor while treating atomic text elements and line breaks as boundaries.
- Prefer the nearest editable mention when file, skill, and plugin candidates compete, and avoid treating common uppercase environment variables as skill queries.
- Insert a separator before completions adjacent to atomic elements and keep dismissed popup state scoped to the matching editable token occurrence.
## Testing
- Add unit and snapshot coverage for adjacent and partially bound mentions, whitespace boundaries, shell variables, popup dismissal, and file, image, skill, and plugin insertion.
GitOrigin-RevId: 352a42c7bcdbf4f6cd35368ced25f71926fdbc1b
## Why
The skill toggle view truncated every display name to 21 characters, even when
the popup had enough room to show more. This could hide the part that
distinguishes similarly named skills.
## What changed
- Pass full skill display names to the shared row renderer so it can fit them
to the available width alongside descriptions.
- Keep narrow layouts bounded by the renderer's width-aware truncation.
## Testing
Added coverage for preserving and filtering full display names, plus snapshots
for wide and narrow popup layouts.
GitOrigin-RevId: c434237e00440f920d8aa9c365b6bda5e68e0941
## Why
A pasted user message can contain a raw CSI sequence that corrupts
terminal scrollback, making the latest response or input appear missing.
The same sequence may already be present in persisted history when an
affected conversation is resumed.
## What changed
- Remove CSI sequences and non-whitespace control characters from
explicit paste input before it enters the composer.
- Apply the same small transform before rich and raw `UserHistoryCell`
rendering, so existing conversations render safely without mutating
stored message content.
## Summary
Selection rows reserve their first two columns for the selection cursor
and render into the menu surface's left gutter. The empty-state message
reused that shifted row area even though it has no cursor prefix, so `no
matches` appeared two columns to the left of the search field.
This renders empty lists at the inset list origin while preserving the
cursor gutter for non-empty rows.
## Summary
Autocomplete completion previously inserted a new space even when a
separator was already present, which could leave redundant whitespace
around neighboring text. Popup dismissal also tracked only the query
string, so dismissing one token could suppress a different occurrence
with the same text.
This gives completions one horizontal-separator policy across files,
images, skills, and mentions. Existing separators are reused when
possible, ordinary suffix text remains separated, and line breaks stay
intact. Dismissal now identifies the complete whitespace-delimited token
occurrence, so offset-only edits preserve dismissal while later
identical tokens remain independent. Newly completed values beginning
with `$` or `@` can also remain closed when the next PR's affinity rule
recognizes the token to the left of the cursor.
## Examples
The examples below use `|` to represent the cursor.
### Reuse existing separators
Starting with an existing two-space gap:
```text
@ma| next
```
After accepting `src/main.rs` and typing `foo`, completion previously
added a third separator and left both original spaces before `next`:
```text
src/main.rs foo| next
```
After this PR, completion reuses the first existing separator as the
insertion point and preserves the second between the new text and
`next`:
```text
src/main.rs foo| next
```
### Keep a completed sigil-prefixed value closed
Starting before a line break:
```text
@ma|
next
```
and accepting a path whose result is itself prefixed with `@` produces:
```text
@scope/main.rs |
next
```
Without this PR's completion dismissal, the affinity rule in #30463
rediscovers the completed token to the left of the cursor and reopens
its popup:
```text
@scope/main.rs |
^^^^^^^^^^^^^^^ popup reopens
next
```
With this PR, the inserted occurrence is dismissed and the popup remains
closed:
```text
@scope/main.rs |
^^^^^^^^^^^^^^^ popup remains closed
next
```
### Do not dismiss an identical later occurrence
Given two identical tokens:
```text
@scope/main.rs| @scope/main.rs
```
after dismissing the first popup with Escape and moving to the second
token, query-only dismissal previously suppressed the second popup too:
```text
@scope/main.rs @scope/main.rs|
^^^^^^^^^^^^^ popup remains closed
```
After this PR, dismissal also matches the token's ordinal among complete
tokens, so the second occurrence opens normally:
```text
@scope/main.rs @scope/main.rs|
^^^^^^^^^^^^^ popup opens
```
### Keep dismissal across offset-only edits
After dismissing `@ma`, moving to its start, and pasting an email-like
token:
```text
email@ma.com @ma|
```
the `@ma` bytes embedded in `email@ma.com` do not count as another
autocomplete token. The original `@ma` keeps its dismissal even though
its byte range moved.
## Stack
This is PR 2 of 3, stacked on #31190. It relies on the explicit
replacement ranges introduced there and provides the completion
lifecycle used by the targeting fix in #30463.
## Summary
Autocomplete popup synchronization already identifies the active token's
range and query, but accepting a result previously discarded that range
and independently recomputed token boundaries around the cursor. Those
calculations could disagree at ambiguous cursor positions. For example,
in `@first| @second` (with `|` marking the cursor at the intervening
space), the popup targets `@second`, while the old acceptance path
replaces `@first`.
This threads the active range through legacy file search, skill
mentions, and mentions-v2 completion. File, image, and mention insertion
now replace the same token that supplied the popup query, and image
completion reuses the shared file insertion path.
## Stack
This is PR 1 of 3. #31191 builds separator and dismissal behavior on
these explicit replacement ranges, and #30463 then fixes token affinity
between adjacent mentions.
This is the final plugin sharing PR in the 5-PR stack. It applies the
remaining TUI polish for remote plugin catalog rows and tabs:
admin-disabled plugins now read as blocked/view-only instead of looking
toggleable, admin-installed/default-installed plugins count and sort
like installed plugins, plugin search matches richer metadata, and an
empty successful `Shared with me` section stays hidden.
- Admin-disabled rows use a blocked marker, show `Disabled`, and keep
Enter-only detail behavior without a toggle hint.
- Admin-installed/default-installed plugins show as installed in counts,
ordering, tabs, and detail copy.
- Plugin search now matches descriptions and keywords in addition to
existing row metadata.
- Successful-empty `Shared with me` tabs are hidden, while loading,
error, workspace-empty, and real shared-plugin states remain visible.
- Updates coverage in
`plugins_popup_snapshot_shows_all_marketplaces_and_sorts_installed_then_name`,
`plugins_popup_admin_disabled_installed_plugin_has_no_toggle_hint`,
`plugins_popup_search_matches_plugin_descriptions`, and
`plugins_popup_remote_section_fallback_states_snapshot`.
- Updates snapshots `plugins_popup_curated_marketplace` and
`plugins_popup_empty_shared_section_hidden`.
<img width="2034" height="106" alt="image"
src="https://github.com/user-attachments/assets/3f9a57e1-edd8-4e6c-b0b0-9f632a3c9529"
/>
<img width="2038" height="380" alt="image"
src="https://github.com/user-attachments/assets/45a47491-3381-4846-a13d-496bc0051d42"
/>
## Summary
- Add `iconAssets` and `iconDarkAssets` to the app-list protocol.
- Preserve structured icons through directory merging and the connector,
app-
server, and TUI boundaries.
- Keep legacy logo URLs unchanged as compatibility fallbacks.
- Update generated protocol schemas and TypeScript types.
## Why
Connector metadata is consumed by connector discovery, ChatGPT
integration, core, and TUI code. Treating app-server's wire DTO as the
shared domain model reverses the intended dependency direction.
## What changed
- Added connector-owned app branding, review, screenshot, metadata, and
info types.
- Added explicit conversions in app-server and TUI while preserving
app-server's wire payloads.
- Removed production app-server-protocol dependencies from connectors
and ChatGPT connector code.
## Stack
This is PR 4 of 6, stacked on [PR
#29722](https://github.com/openai/codex/pull/29722). Review only the
delta from `codex/split-config-layer-types`. Next: [PR
#29724](https://github.com/openai/codex/pull/29724).
## Validation
- Connector and tools coverage passed.
- App-server app-list coverage passed: 13 tests.
## Why
The TUI treats both an active turn and MCP startup as a running task.
That currently blocks `/resume` and several settings commands even
though they do not compete with turn execution, which is especially
frustrating when MCP startup is slow.
Model, permissions, personality, and service-tier selections already
update thread settings independently of the running turn. Other clients
can send those updates mid-turn, while the current turn continues with
its captured settings. Allowing the same updates from local slash
commands makes the TUI consistent with that existing behavior.
## What changed
- Allow `/resume` while a task is running.
- Allow `/model`, `/permissions`, `/personality`, and service-tier
commands such as `/fast` while a task is running.
- Keep the existing behavior where the active turn uses its captured
settings and updates apply to subsequent turns.
- Exercise the commands under the busy state in the existing TUI tests
and retain coverage for commands that should remain blocked.
## Behavior note
Turn settings such as model selection and reasoning effort are captured
when a turn starts. Changing them during an active turn affects the next
turn, not the turn already in progress. The status bar updates
immediately, so it may temporarily display the newly selected setting
before that setting is actually in effect.
## Verification
- Focused `codex-tui` tests for resume dispatch, settings popups,
`/fast`, and disabled-command behavior.
## Related issues
Closes#19015.
Addresses the next-turn-safe model/reasoning switching portion of
#14356; dedicated shortcuts and the proposed depth meter remain out of
scope.
## Summary
[@milanglacier reported in
#28653](https://github.com/openai/codex/issues/28653) that the active
mention candidate is hard to distinguish. I suspect [@binbjz’s #28500
report](https://github.com/openai/codex/issues/28500) _(where arrow-key
navigation appeared not to work)_ may describe the same presentation
problem: the selection may have been changing, but the UI was not
showing the active row clearly in their terminal. This PR makes two
small changes to the selection indication behavior:
- Reserve a two-character gutter and mark the active candidate with `> `
for color-agnostic indicator coverage.
- Apply the shared theme-aware accent to the entire selected row for
extra emphasis.
- Update the existing popup snapshot.
Reverse-video styling was considered, but avoided it because it is
overly dependent on the user’s terminal palette.
<img width="2046" height="482" alt="image"
src="https://github.com/user-attachments/assets/b5eb62c3-fd24-4c09-906e-7bd66913b5c6"
/>
## Testing
- `just test -p codex-tui default_unified_mention_popup_snapshot`
- `just clippy -p codex-tui`
- `just fmt`
- Compiled `codex-cli` and tested the unified mentions picker in the
terminal.
Fixes#28104.
## Summary
Active `/goal` turns should leave the persisted goal paused whenever the
TUI interrupts the running turn. The bug in #28104 showed this most
visibly through `Esc`: some interrupt paths aborted the turn without
updating the goal status, so the goal could remain active and continue
automatically.
This change makes `ChatWidget` pause an active goal before the TUI sends
an interrupt from the status-row path, the pending-steer path, `Ctrl+C`,
or a request-user-input overlay. The modal overlay now reports whether a
key will interrupt the turn, which keeps modal `Esc` and `Ctrl+C`
behavior aligned with the normal interrupt paths.
## Manual Testing
Built the local CLI with `just codex --help`, then launched the local
TUI with goals enabled. Started an active `/goal` turn and interrupted
it with `Esc`, then resumed and repeated with `Ctrl+C`; both paths
showed `Goal paused`, the interrupted-conversation message, and the
`Goal paused (/goal resume)` footer. I also stopped the background
terminal and exited the TUI cleanly after the run.
I did not find a reliable standalone manual path to force the
request-user-input overlay case, so that path is covered by the focused
automated test.
## Why
Command approval cache keys included the command and working directory,
but not the execution environment. An approval for `/workspace` locally
could therefore be reused for the same command and path on an executor.
## What changed
- Include the selected environment ID in shell and unified-exec approval
cache keys.
- Carry that ID through the normal command approval request so clients
can show which environment is being approved.
- Expose the environment through app-server as a required nullable
`environmentId` and show it in the inline TUI approval prompt.
- Keep older recorded approval events compatible when the environment is
absent.
For example, `echo ok` in local `/workspace` and `echo ok` in executor
`/workspace` now produce different approval keys and separate prompts.
## Scope
This PR does not change network approvals, Guardian review actions, MCP
elicitation, full-screen TUI rendering, or environment-ID validation.
Remote `shell_command` execution itself remains in #28722; this PR only
makes its approval key environment-aware.
## Why
Clients running an app-server on one OS and an exec-server on another OS
need to be able to pass sandbox config to app-server that refers to
resources on the executor's foreign OS.
## What
`AbsolutePathBuf` can't represent these paths and we don't want users to
be exposed to `PathUri` yet, so this moves the public app-server API to
be expressed in terms of `ApiPathString`.
Stacked on #28165.
- change app-server v2 filesystem permission paths, including legacy
read/write roots, to `ApiPathString`
- localize API paths through `PathUri` when converting into the current
native core permission types
- make path-bearing permission conversions fallible and surface
localization failures instead of silently treating malformed grants as
ordinary denials
- propagate conversion failures through app-server and TUI approval
handling
- regenerate the app-server JSON and TypeScript schemas
- leave migration TODOs on native-path conversions so they can be
removed once core permission paths use `PathUri`
## Summary
- Add TUI auto-resolution handling for `request_user_input` prompts when
`autoResolutionMs` is present.
- Use a 60s hidden grace period followed by a 60s visible countdown,
then submit an empty answer response if the user does not interact.
- Snooze auto-resolution on key or paste interaction and add
snapshot/test coverage for the countdown UI.
## Notes
- The TUI currently treats `autoResolutionMs` as an enable signal and
intentionally does not use the provided duration value for the countdown
policy.
### Auto resolution
https://github.com/user-attachments/assets/5323152f-2ece-4aba-b75d-c32aa776f544
### Snooze after interaction
https://github.com/user-attachments/assets/100d54c4-3a41-4c6c-9c07-cd28075a0d62
## 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.
## 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.
## 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.
## 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
The TUI rejected playful `/goal` spellings such as `/goooooooooooal`,
even though Codex Apps accepts them for the World Cup promotion. This
keeps the TUI behavior consistent without changing how the canonical
command is presented.
## How it works
Built-in command lookup recognizes lowercase `go+al` as the existing
`goal` command after normal exact-name parsing fails. The command
catalog remains unchanged, so autocomplete continues to advertise
`/goal` normally.
## Verification
Added lookup-level and end-to-end TUI coverage for the flexible
spelling. The focused tests, scoped Clippy checks, and formatting pass.
The full `codex-tui` suite passed 2,833 of 2,835 tests; the two failing
guardian feature-flag tests reproduce unchanged on fresh `origin/main`.
## Why
`request_user_input` is moving beyond its original plan-mode-only
workflow, and future default/goal-mode usage needs a way for the model
to ask helpful but non-blocking questions without forcing the turn to
wait forever. This PR adds an explicit `autoResolutionMs` contract so a
later client/runtime change can auto-resolve unanswered prompts after a
bounded window while leaving truly blocking questions unchanged.
This is contract plumbing only; it does not implement the client-side
timer or auto-selection behavior, and the model-facing description
treats the field as reserved unless the current runtime explicitly
supports auto-resolution.
## What Changed
- Added optional `autoResolutionMs` to the model-facing
`request_user_input` args and core `RequestUserInputEvent`.
- Added model-facing schema text for `autoResolutionMs` while marking it
reserved for runtimes that explicitly support auto-resolution.
- Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
normalization by clamping out-of-range model-provided values.
- Propagated the field through app-server v2
`ToolRequestUserInputParams`, app-server request forwarding, generated
TypeScript, and JSON schema fixtures.
- Updated app-server, core, protocol, and TUI call sites/tests so
omitted values preserve existing `None`/`null` behavior and coverage
verifies a `Some(60_000)` round trip.
## Verification
- `just test -p codex-app-server-protocol`
- `just test -p codex-core request_user_input`
- `just test -p codex-app-server request_user_input_round_trip`
- `just test -p codex-tui request_user_input`
- `just test -p codex-protocol`
## Stack
1. **[1 of 3] Support long raw TUI goal objectives** - this PR
2. [2 of 3] Support long pasted text in TUI goals - #27509
3. [3 of 3] Support images in TUI goals - #27510
## Why
`thread/goal/set` limits persisted objective text to 4000 characters.
The TUI used to reject raw `/goal` objectives above that limit, even
though the client can make them usable by writing the long text to a
file and storing a short objective that points at that file.
This also needs to work for remote app-server sessions: filesystem API
calls must create files on the app-server host, and the stored path must
be meaningful to the agent on that host.
## What Changed
- Adds an app-server-host path helper so TUI code can build paths that
are resolved on the app-server host rather than the TUI host.
- Adds TUI app-server session helpers for `fs/createDirectory`,
`fs/writeFile`, `fs/readFile`, and `fs/remove` that work for embedded
and remote app-server sessions without changing the app-server protocol.
- Materializes oversized raw `/goal` objectives into
`$CODEX_HOME/attachments/<uuid>/goal-objective.md` through the
app-server filesystem APIs, then stores a short, readable objective that
directs the agent to that file.
- Reads managed objective files back for `/goal edit`. Other goal UI
renders the readable stored objective normally, without
managed-file-specific presentation logic.
- Recognizes managed references only when they name the expected
generated file under the app server's reported `$CODEX_HOME`, and cleans
up newly materialized files when goal replacement or setting does not
complete.
## Verification
- Added/updated TUI tests for raw oversized `/goal` submission, large
inline-paste expansion, queued oversized goals, app-facing
materialization before `thread/goal/set`, managed-path validation,
editing, and cleanup.
- Added/updated app-server-client remote coverage for initialized remote
Codex home handling.
## Manual Testing
- Ran the real TUI against a Unix-socket app server with different local
and server `$CODEX_HOME` directories. Oversized goals wrote only under
the server home, and persisted references used the server-canonical path
rather than the TUI path.
- Exercised 3,999-, 4,000-, and 4,001-character raw objectives. The
first two stayed inline without new files; the 4,001-character objective
became a managed objective file.
- Submitted a larger 8,275-character objective, verified its full
contents on the app-server host, and observed the goal continuation open
the referenced server-side file.
- Opened `/goal edit` for a managed objective and verified the full text
was restored through remote `fs/readFile`.
- Submitted an oversized replacement while a goal was active, verified
no file was written before confirmation, then canceled and confirmed
that the existing goal and attachment count were unchanged.
## Why
Pressing `Esc` on a turn that produced no visible output restores the
submitted prompt so the user can keep editing it. That restore path
preserved the prompt content, images, and mention bindings, but left the
composer cursor at the start of the restored text. The next edit
therefore inserted at the beginning instead of continuing from the end
of the prompt.
## What Changed
- Move the cursor to the end after
`BottomPane::set_composer_text_with_mention_bindings` rehydrates a
restored draft.
- Add test-only cursor accessors so restore tests can assert the
composer state directly.
- Extend the queued restore regression to assert the restored composer
cursor is positioned at `text.len()`.
## How to Test
Manual reviewer flow:
1. Start Codex in the TUI.
2. Submit a prompt that will take long enough to interrupt.
3. Press `Esc` before any visible assistant output appears.
4. Confirm the prompt is restored into the composer and the cursor is at
the end, so typing appends to the prompt.
5. Repeat with a prompt that includes an attached image or resolved
mention and confirm the restored content remains intact.
Targeted tests:
- `just test -p codex-tui
chatwidget::tests::composer_submission::queued_restore_with_remote_images_keeps_local_placeholder_mapping`
Lint note:
- `just argument-comment-lint` is blocked locally by the existing Bazel
`compiler-rt` empty glob failure before analyzing touched code. The
touched Rust diff was manually inspected and adds no new opaque
positional literal callsites.
Fixes#26025.
## Why
`/goal edit` opens `CustomPromptView`, which did not use the paste-burst
handling that protects the main composer when terminals deliver paste as
rapid key events. On Windows terminals, the first pasted newline could
be treated as Enter-to-submit, truncating the goal edit and leaving the
rest of the paste behind.
## What
This reuses `PasteBurst` in `CustomPromptView` as a lightweight
Enter-suppression detector for paste-like key streams. Characters still
insert directly, explicit paste still goes through the view paste path,
and ordinary text entry still submits on Enter.
## Stack
1. #25850 - Key request-permission grants by environment: stores and
applies sticky permission grants per environment id.
2. #25858 - Add `environmentId` to `request_permissions`: lets the model
target a selected environment and resolves relative permission paths
against it.
3. This PR (#25862) - Propagate permission approval environment id:
carries the selected environment id through approval events, app-server
requests, TUI prompts, and delegate forwarding.
4. #25867 - Add remote request permissions integration coverage:
verifies the selected remote environment across request, approval, grant
reuse, and exec.
This PR is stacked on #25858, and #25867 is stacked on this PR.
## Why
PR2 lets the model bind a `request_permissions` call to a selected
environment, but the approval event and client-facing request still
needed to carry that binding. For CCA, the user-facing prompt and
delegated approval path should know which environment the grant applies
to instead of relying on cwd alone.
## What Changed
- Added optional `environmentId` to `RequestPermissionsEvent`.
- Emit the selected environment id from core permission approval events.
- Preserve the environment id through delegate forwarding, including
cwd-based delegated requests.
- Added `environmentId` to app-server permission approval params,
generated schema/TypeScript artifacts, and README examples.
- Preserve and display the environment id in TUI permission approval
prompts.
- Updated focused core, app-server protocol, and TUI conversion
coverage.
## Testing
Not run locally per instruction. Performed read-only `git diff --check`.
## Why
The TUI shortcut overlay used static labels for `Tab` and `Ctrl+C`, even
though both keys change behavior while a task is running. That made the
visible help misleading: idle `Tab` submits rather than queues, and
active-turn `Ctrl+C` interrupts rather than exits.
Closes#25531.
Closes#25564.
## What Changed
- Pass task-running state into the shortcut overlay renderer.
- Render `Tab` as `submit message` while idle and `queue message` while
work is running.
- Render `Ctrl+C` as `exit` while idle and `interrupt` while work is
running.
- Add snapshot coverage for the active-work shortcut overlay and update
idle overlay snapshots.
## How to Test
1. Start Codex and open the shortcut overlay with `?` while no task is
running.
2. Confirm the overlay shows `tab to submit message` and `ctrl + c to
exit`.
3. Start a task, then open or keep the shortcut overlay visible while
work is running.
4. Confirm the overlay shows `tab to queue message` and `ctrl + c to
interrupt`.
5. Type a follow-up prompt during active work and press `Tab`; confirm
it queues rather than submitting immediately.
Targeted tests:
- `just test -p codex-tui footer_snapshots`
- `just test -p codex-tui footer_mode_snapshots`
## Validation Notes
`just test -p codex-tui` currently has two unrelated guardian
feature-flag test failures on this base:
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
`just argument-comment-lint codex-rs/tui/src/bottom_pane/footer.rs`
could not run locally because the prebuilt wrapper requires `dotslash`;
the touched Rust diff was manually inspected for opaque positional
literals.
Closes#24886.
## Why
Users can configure the TUI status line and terminal title with
`model-with-reasoning`, but issue #24886 asks for a compact
reasoning-only item. That lets a setup show just `default`, `low`,
`medium`, `high`, or `xhigh` without repeating the model name.
## What changed
- Added a `reasoning` item for `/statusline` and `/title` setup flows.
- Rendered the item from the effective reasoning effort, including
collaboration-mode overrides.
- Registered `reasoning` with `codex doctor` so Codex-generated
terminal-title config is not reported as invalid.
- Updated TUI setup snapshots so the picker previews include the new
item.
## Summary
Fixes#25295.
The slash-command popup reused its previous `ScrollState` when the
composer filter token changed. After scrolling the full `/` command
list, typing a narrower filter such as `/st` could clamp the stale
selection into the filtered results and highlight the wrong command.
This resets the popup selection and viewport only when the parsed filter
token changes, so normal arrow navigation is preserved while new filters
start at the first match.
## Summary
I frequently want to be able to paste into the searchable menu -- the
most common use-case here is when specifying an upstream for a
`/review`, where I copy the upstream from an open terminal.
## TL;DR
When you press Esc or Ctrl+C after sending a prompt but before any
output was rendering, it restores the last composer and the message.
## Summary
Cancelling a prompt immediately after submission should behave like
returning to edit that prompt, not like discarding the user's draft.
Today, pressing `Esc` or `Ctrl+C` before Codex responds leaves the
submitted prompt in the transcript and returns an empty composer,
forcing the user to recall or retype it.
When an interrupted turn has not produced substantive visible output,
restore its submitted prompt directly into the composer and roll back
that latest turn. This also covers the first prompt in a fresh thread,
before the TUI has retained a local user-history cell. The restored
draft keeps its text, image attachments, and active collaboration mode
so it can be edited and resubmitted in place.
Restoration is intentionally suppressed once the turn has produced
user-visible activity such as assistant output, tool work, hooks, or
patches. A transient thinking status does not make the prompt
ineligible. Rollback also rebuilds terminal scrollback from the retained
transcript cells so repeated cancellations and terminal resizes do not
duplicate history.
## How to Test
1. Start the TUI with `cargo run -p codex-cli --bin codex`.
2. In a fresh thread, submit the first prompt and press `Esc` before
Codex emits substantive output. Confirm that the prompt returns to the
composer for editing and its submitted transcript row is removed.
3. Repeat with `Ctrl+C`, then repeat after at least one completed turn.
Confirm the same behavior.
4. Submit a prompt, wait for assistant output or tool activity, then
cancel. Confirm that the transcript remains intact and the prompt is not
restored into the composer.
5. Cancel several output-free prompts and resize the terminal between
attempts. Confirm that the startup banner, tip, and transcript history
do not duplicate in scrollback.
Targeted tests:
- `just test -p codex-tui cancelled_turn_edit_restores_prompt`
- `just test -p codex-tui
output_free_interrupted_turn_requests_prompt_restore`
- `just test -p codex-tui
visible_output_prevents_cancelled_turn_prompt_restore`
- `just test -p codex-tui
thinking_status_keeps_cancelled_turn_prompt_restore_eligible`
- `just test -p codex-tui
patch_activity_prevents_cancelled_turn_prompt_restore`
The full `just test -p codex-tui` run completed with `2746` passing
tests and two unrelated existing guardian feature-flag failures. `just
argument-comment-lint` remains blocked locally by the existing Bazel
LLVM `compiler-rt` sanitizer-header glob failure; the touched Rust diff
was manually audited for positional literal comments.
## Summary
PR 3 of 5 in the cloud-managed config client stack.
Adds enterprise-managed cloud config as a first-class config layer
source. The layer metadata is preserved through config loading,
diagnostics, debug output, hook attribution, and app-server protocol
surfaces.
## Details
- Enterprise-managed config becomes a normal config layer source with
backend-supplied `id` and display `name` attached for provenance.
- These layers are designed to behave like non-file managed config: they
can surface syntax/type diagnostics by layer name even though there is
no physical config file.
- Relative path settings are resolved from a stored config base so
cloud-delivered config remains consistent with existing MDM-delivered
config semantics.
- Hook attribution distinguishes config-delivered hooks from
requirements-delivered hooks via `HookSource::CloudManagedConfig`.
- This remains pull-based and snapshot-oriented; the PR adds layer
identity/diagnostics, not dynamic reload behavior.
## Validation
Validated through the targeted stack checks after rebasing onto current
`main`:
- Rust crate tests for
config/hooks/cloud-config/backend-client/app-server-protocol
- Filtered `codex-core` and `codex-app-server` `cloud_config_bundle`
tests
- Python generated-file contract test
- `cargo shear --deny-warnings`
- Targeted `argument-comment-lint` for config/hooks
## Summary
- add Vim normal-mode `s` support to substitute the character under the
cursor and enter insert mode
- fix Vim normal-mode `o` so opening below the final line moves the
cursor onto the new blank line
- update keymap config/schema and keymap picker snapshots for the new
action
## Validation
- `just fmt`
- `just write-config-schema`
- `just test -p codex-config`
- focused `just test -p codex-tui` coverage for the Vim `s` and `o`
behavior, keymap conflict handling, and keymap picker snapshots
- `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
- `git diff --check`
## Notes
A full `just test -p codex-tui` run still has two unrelated Guardian
feature-flag failures in this checkout:
-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
## Why
TUI users can archive saved sessions from other surfaces, but there is
no in-session command for archiving the active session. Since archiving
the active session also exits the TUI, the command should ask for
explicit confirmation instead of firing immediately.
I'm also working on [a companion
PR](https://github.com/openai/codex/pull/25021) that adds `codex
archive` and `codex unarchive` top-level CLI commands.
## What changed
- Adds a new `/archive` slash command described as `archive this session
and exit`.
- Shows a confirmation dialog with `No, don't archive` selected first
and `Yes, archive and exit` as the explicit action.
- On confirmation, calls the existing `thread/archive` app-server RPC
for the active main session and exits after success.
- Keeps `/archive` disabled while a task is running and unavailable in
side conversations.
## Verification
Added focused TUI coverage for the `/archive` confirmation flow,
disabled-while-task-running behavior, and the `/ar` slash-command popup
snapshot.