Commit Graph
137 Commits
Author SHA1 Message Date
Felipe CouryandGitHub 5e0a4adbe5 feat(tui): add raw scrollback mode (#20819)
## Why

Granular copy is particularly difficult with the current output. Part of
it was solved with the introduction of the `/copy` command but when you
only need to copy parts of a response, you still encounter some issues:

- When you copy a paragraph, the result is a sequence of separate lines
instead of one correctly joined paragraph.
- When a word wraps, part of it stays on the original line and the rest
appears at the start of the next line.
- When you copy a long command, extra line breaks are often inserted,
and command arguments can be split across multiple lines.


https://github.com/user-attachments/assets/0ef85c84-9363-4aad-b43a-15fce062a443

## Solution

Now that we own the scrollback and we re-create it when we resize, we
have the opportunity of toggling between the raw text and the rich text
we see today.

- Add TUI raw scrollback mode with `tui.raw_output_mode`, `/raw
[on|off]`, and the configurable `tui.keymap.global.toggle_raw_output`
action.
- Render transcript cells through rich/raw-aware paths so raw mode
preserves source text and lets the terminal soft-wrap selection-friendly
output.
- Bind raw-mode toggle to `alt-r` by default, with the keybinding path
toggling silently while `/raw` continues to emit confirmation messages.

## Related Issues

Likely addressed by raw mode:

- #12200: clean copy for multiline and soft-wrapped output. Raw mode
removes Codex-inserted wrapping/indentation and lets the terminal
soft-wrap logical lines.
- #9252: command suggestions gain unwanted leading spaces when copied.
Raw mode renders transcript text without the rich-mode left
padding/gutter.
- #8258: prompt output is hard to copy because of leading indentation.
Raw mode renders user/source-backed transcript text without that
decorative indentation.

Partially or conditionally addressed:

- #2880: copy/export message as Markdown. Raw mode exposes raw Markdown
for terminal selection, but this PR does not add a dedicated
export/copy-message command.
- #19820: mouse drag selection + copy in the TUI. Raw mode improves
terminal-native selection of output/history text, but this PR does not
implement in-TUI mouse selection, highlighting, auto-copy, or composer
selection.
- #18979: copied content is divided into two parts. This should improve
cases caused by Codex-inserted wraps/padding in rendered output; if the
report is about pasting into the composer/input path, that remains
outside this PR.

## Validation

- `just write-config-schema`
- `just fmt`
- `cargo test -p codex-config`
- `cargo test -p codex-tui`
- `just fix -p codex-tui`
- `just argument-comment-lint`
- `cargo test -p codex-tui
raw_output_mode_can_change_without_inserting_notice -- --nocapture`
- `cargo test -p codex-tui
raw_slash_command_toggles_and_accepts_on_off_args -- --nocapture`
- `cargo test -p codex-tui raw_output_toggle -- --nocapture`
- `git diff --check`
- `cargo insta pending-snapshots`
2026-05-05 11:17:47 -07:00
Eric TrautandGitHub f09e1936e0 Validate /goal objective length in TUI (#20746)
## Why

Long `/goal` definitions currently reach lower-level goal validation and
can produce an opaque failure. This bug was reported by a user. Pasted
instruction blocks are especially confusing because the composer can
still contain a paste placeholder before expansion, which may otherwise
fall into the generic prompt-size error path.

There was also a related paste edge case where `/goal ` followed by a
multiline block whose first pasted line was blank looked like a bare
`/goal` command. That showed the goal usage/summary instead of setting
the pasted objective.

## What Changed

This adds TUI-side preflight validation for `/goal <objective>` using
the shared `MAX_THREAD_GOAL_OBJECTIVE_CHARS` limit. Oversized typed,
queued, and pasted goal objectives now fail locally with a goal-specific
message that recommends putting longer instructions in a file and
referencing that file from the goal.

The TUI now also lets inline-argument slash commands consume later-line
arguments before treating the first line as a bare command, so `/goal `
followed by blank lines and then objective text sets the goal instead of
opening the bare `/goal` flow.

## Manual Testing

1. Start the TUI with goals enabled and an active session.
2. Submit `/goal ` followed by exactly 4,000 objective characters. It
should continue through the normal goal-setting path.
3. Submit `/goal ` followed by 4,001 objective characters. It should not
set a goal, and should show `Goal objective is too long: 4,001
characters. Limit: 4,000 characters.` followed by the guidance to put
longer instructions in a file and reference that file from the goal.
4. Type `/goal `, paste a large block that becomes a `[Pasted Content
... chars]` placeholder, then submit. It should validate the expanded
pasted text and show the goal-specific file guidance rather than the
generic prompt-size error.
5. Type `/goal `, paste a multiline block whose first line is blank,
then submit. It should set the objective from the non-blank pasted
content instead of showing `Usage: /goal <objective>` or the bare goal
summary.
6. While a turn is running, queue an oversized `/goal` command. When the
queue drains, it should show the same goal-specific error and should not
emit a goal-setting request.
2026-05-05 09:55:07 -07:00
9d579813bb 1- Add model service tiers metadata (#20969)
## Why

The model list needs to carry display-ready service tier metadata so
clients can render tier choices with stable IDs, names, and
descriptions. A raw speed-tier string list is not enough for richer UI
copy or future tier labels.

## What changed

- Added `ModelServiceTier` to shared model metadata with string `id`,
`name`, and `description` fields.
- Added `service_tiers` to `ModelInfo` and `ModelPreset`, preserving
empty defaults for older cached model payloads.
- Exposed `serviceTiers` on app-server v2 `Model` responses and threaded
it through TUI app-server model conversion.
- Marked legacy `additional_speed_tiers` / `additionalSpeedTiers`
metadata as deprecated in source and generated schema output.
- Regenerated app-server protocol JSON schema and TypeScript fixtures,
including `ModelServiceTier.ts`.

## Verification

- Ran `just write-app-server-schema`.
- Did not run local tests per repo instruction; relying on PR CI.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-05-05 09:51:18 +03:00
alexsong-oaiandGitHub 3ad7cf0993 Add plugin ID to skill analytics (#20923)
## Summary
- thread plugin skill roots through the skills loader with their plugin
ID
- store plugin ID on loaded skill metadata for plugin-provided skills
- include plugin ID on skill invocation analytics events

## Test plan
- cargo check -p codex-core-skills
- cargo check -p codex-core -p codex-core-plugins -p codex-analytics
- cargo check -p codex-tui
- cargo check -p codex-plugin -p codex-core -p codex-core-plugins -p
codex-analytics
- cargo check -p codex-app-server
- cargo test -p codex-analytics
- HOME=/private/tmp/codex-empty-home cargo test -p codex-core-skills
- just fix -p codex-core-skills
- just fix -p codex-analytics
- just fix -p codex-core-plugins
- just fix -p codex-core
- just fmt
- git diff --check
2026-05-04 20:36:29 -07:00
rhan-oaiandGitHub aee1fe2659 [codex-analytics] add item lifecycle timing (#20514)
## Why

Tool families already disagree on what their existing `duration` fields
mean, so lifecycle latency should live on the shared item envelope
instead of being inferred from per-tool execution fields. Carrying that
envelope through app-server notifications gives downstream consumers one
reusable timing signal without pretending every tool has the same
execution semantics.

## What changed

- Adds `started_at_ms` to core `ItemStartedEvent` values and
`completed_at_ms` to core `ItemCompletedEvent` values.
- Populates those timestamps in the shared session lifecycle emitters,
so protocol-native items get timing without each producer tracking its
own clock state.
- Exposes `startedAtMs` on app-server `item/started` notifications and
`completedAtMs` on `item/completed` notifications.
- Maps the lifecycle timestamps through the app-server boundary while
leaving legacy-converted notifications nullable when no lifecycle
timestamp exists.
- Regenerates the app-server JSON schema and TypeScript fixtures for the
notification-envelope change and updates downstream fixtures that
construct those notifications directly.
- Extends the existing web-search and image-generation integration flows
to assert the new lifecycle timestamps on the native item events.

## Verification

- `cargo check -p codex-protocol -p codex-core -p
codex-app-server-protocol -p codex-app-server -p codex-tui -p codex-exec
-p codex-app-server-client`
- `cargo test -p codex-core --test all web_search_item_is_emitted`
- `cargo test -p codex-core --test all
image_generation_call_event_is_emitted`
- `cargo test -p codex-app-server-protocol`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20514).
* #18748
* #18747
* #17090
* #17089
* __->__ #20514
2026-05-04 22:33:20 +00:00
Felipe CouryandGitHub 48402be6fa feat(tui): improve TUI keymap coverage (#20798)
## Summary
- normalize terminal-emitted C0 control characters through configurable
editor keymaps, covering raw control-key fallbacks like
Shift+Enter-as-LF in terminals from #20555 and #20898, plus part of the
modified-Enter behavior in #20580
- add default-unbound keymap actions for toggling Fast mode and killing
the current composer line, giving #20698 users a configurable zsh-style
Ctrl+U option without changing the existing default Ctrl+U behavior
- wire the new actions through gated /keymap picker entries, schema
generation, and snapshot coverage

Fixes #20555.
Fixes #20898.

## Testing
- just write-config-schema
- just fmt
- cargo test -p codex-config
- cargo test -p codex-tui keymap::tests
- cargo test -p codex-tui bottom_pane::textarea::tests
- cargo test -p codex-tui keymap_setup::tests
- cargo insta pending-snapshots
- just fix -p codex-tui
- git diff --check
- just argument-comment-lint
2026-05-04 19:18:56 +00:00
Felipe CouryandGitHub cc16995cc6 feat(tui): add PR summary statusline items (#20892)
## Why?

The Codex App already exposes branch and PR context in its
branch-details UI. This brings the same context into the CLI footer as
opt-in statusline items, so users can choose the extra signal without
making the default footer busier.

## What?

Add optional `pull-request-number` and `branch-changes` items to the
configurable TUI status line.

- `pull-request-number` shows the open PR for the current checkout and
renders as a clickable terminal hyperlink when OSC 8 links are
supported.
- `branch-changes` shows committed additions/deletions against the
repository default branch, or `No changes` when the branch has no
committed diff.

<img width="1257" height="261" alt="CleanShot 2026-05-03 at 20 44 15"
src="https://github.com/user-attachments/assets/10b4380b-c3e9-4729-9ee1-3f742068fa47"
/>

## Architecture

This follows the same client/app-server split as the Codex App: the TUI
owns presentation, caching, and optional rendering, while
workspace-sensitive `git` and `gh` discovery runs through app-server.

The new TUI-local `workspace_command` layer sends bounded,
non-interactive `command/exec` requests to the active app-server. That
makes the implementation remote-friendly: the TUI does not decide
whether commands run in an embedded local workspace or a remote
workspace, and it does not bypass app-server sandbox or permission
policy.

The branch summary logic stays internal to `codex-tui` because this PR
only needs TUI statusline behavior. The command boundary is still
isolated behind `WorkspaceCommandExecutor`, so the lookup code can be
lifted or reused later without changing statusline rendering.

## How?

- Add a TUI `WorkspaceCommandExecutor` abstraction backed by app-server
`command/exec`.
- Add branch summary probes for:
  - current branch name,
  - open PR metadata,
  - committed branch diff stats against the default branch.
- Prefer remote-tracking default branch refs for diff stats, avoiding
stale or absent local `main` branches.
- Resolve PRs with `gh pr view` first, then fall back to
commit-associated PR lookup across parent/fork repos.
- Add `/statusline` picker entries, preview values, rendering, and OSC 8
clickable PR links.
- Keep all probes best-effort so missing `git`, missing `gh`, auth
failures, or non-git directories hide optional items instead of
surfacing footer errors.

## Validation

- `cargo test -p codex-tui branch_summary -- --nocapture`
- Snapshot coverage for the `/statusline` preview/setup rendering paths
- Hyperlink rendering coverage for clickable PR statusline cells
2026-05-04 16:11:15 -03:00
Felipe CouryandGitHub 94800ecbbf feat(tui): add keymap debug inspector (#20794)
## Why

We constantly get bug reports about keys not being recognized by Codex
when the terminal is not handling the key press. Running `/keymap debug`
or `/keymap` and going to the Debug tab, we can allow the user to either
understand that the key being pressed is not being recognized or to
check what it's being recognized as and report or reassign that key.

| Menu | Inspector | Hint |
|---|---|---|
| <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57 12"
src="https://github.com/user-attachments/assets/512b6faa-344e-4aee-9c00-b4bdc633a662"
/> | <img width="1261" height="754" alt="CleanShot 2026-05-02 at 12 56
36"
src="https://github.com/user-attachments/assets/a6ddae7d-e174-4ee4-893f-e6bec4fff4ab"
/> | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57
30"
src="https://github.com/user-attachments/assets/db507784-f40a-4cff-ac23-a61d9703769b"
/> |
## Summary
- add a Debug tab to `/keymap` and support `/keymap debug` for direct
access
- show what key Codex receives, the config key representation, raw event
details, and matching actions
- add a progressive missing-key hint that escalates after a few seconds
with no detected keypress

## Validation
- `just fmt`
- `cargo test -p codex-tui keymap_setup::tests::debug_view`
- `cargo test -p codex-tui keymap_setup::tests`
- `cargo test -p codex-tui slash_keymap`
- `cargo test -p codex-tui` (unit tests passed; integration test
`suite::model_availability_nux::resume_startup_does_not_consume_model_availability_nux_count`
failed locally by itself with `codex resume` exiting 1 and terminal
probe escape output)
- `just fix -p codex-tui`
- `just argument-comment-lint`
- `cargo insta pending-snapshots`
- `git diff --check`
2026-05-04 14:40:50 -03:00
Eric TrautandGitHub 3c2dcbef85 Keep paused goals paused on thread resume (#20790)
## Summary

Early adopters of the `/goal` feature have provided feedback that they
expect a goal they explicitly paused to remain paused when they resume a
thread. Previously, resuming a thread would reactivate a paused goal.

This PR keeps persisted goal status unchanged during thread resume. This
honors the user feedback while also simplifying the core goal logic.

Rather than have the core logic automatically resume a paused goal, that
responsibility is transferred to the client. The TUI now detects a
resumed thread with a paused goal and asks the user whether to `Resume
goal` or `Leave paused`. The prompt appears only for quiet resume flows,
so users who resume with an immediate prompt are not interrupted.

<img width="544" height="111" alt="image"
src="https://github.com/user-attachments/assets/0ac9de1c-6ee6-47ba-b223-c03c8eb4c192"
/>
2026-05-04 08:58:07 -07:00
AbhinavandGitHub d55479488e Clear live hook rows when turns finalize (#20674)
# Why

When a user interrupts a turn while a hook is still running, the normal
turn status is cleared but the separate live hook row can remain visible
as `Running` because the TUI may never receive a matching
`HookCompleted` event before cancellation. Once the turn itself is
finalized, that turn-scoped live state should not remain on screen.

# What

- clear any still-live `active_hook_cell` during turn finalization
- add a regression snapshot covering an interrupted turn with a visible
`PreToolUse` hook row

# Testing

- `cargo test -p codex-tui interrupted_turn_clears_visible_running_hook`
- attempted `cargo test -p codex-tui` (currently aborts on unrelated
existing stack overflow in
`app::tests::discard_side_thread_removes_agent_navigation_entry`)
2026-05-01 14:48:22 -07:00
canvrno-oaiandGitHub 610eefb86b /plugins: add marketplace upgrade flow (#20478)
This PR adds marketplace upgrade to the `/plugins` menu so users can
update configured marketplaces. It adds a `Ctrl+U` shortcut on eligible
marketplace tabs, a loading state, and the app-server request flow
needed to perform `marketplace/upgrade`. After a successful upgrade, the
TUI refreshes plugin data, plugin mentions, and user config so updated
marketplace contents show up across the menu and other plugin surfaces.
It also preserves the current marketplace tab on no-op and failure paths
and surfaces backend error details directly in the TUI.

- Add a `Ctrl+U` upgrade option for user-configured marketplace tabs in
`/plugins`
- Show the upgrade footer hint only on upgradeable marketplace tabs
- Show a loading state during `marketplace/upgrade`
- Surface already-up-to-date and per-marketplace failure results from
the backend
- Refresh plugin data, plugin mentions, and user config after successful
upgrades
- Add tests and snapshot updates for the shortcut flow, loading state,
and failure messaging

Steps to test:
1. Add a `/plugin` marketplace to Codex TUI.
2. Open `/plugins`, move to that marketplace tab, and confirm the footer
shows `Ctrl+U` to upgrade.
3. Press `Ctrl+U` and confirm the popup switches into an upgrade loading
state.
4. When the request finishes, confirm you see the expected result:
updated marketplace contents on success, an already-up-to-date message
on no-op, or backend error details on failure. On no-op or failure,
confirm the popup stays on the same marketplace tab.
2026-05-01 11:26:29 -07:00
Eric TrautandGitHub 6784db51c0 Add /ide context support to the TUI (#20294)
## Why

Users have asked for a `/ide` command in the TUI so Codex can use the
active IDE session for live context such as the current file, open tabs,
and selected ranges. We already support a similar feature in the Codex
desktop app, so bringing it to the TUI makes sense.

One subtle compatibility constraint is that the injected prompt wrapper
and transcript stripping should match the desktop app and IDE extension.
By using the same `## My request for Codex:` delimiter and hiding the
injected context from transcript rendering the same way, threads created
in the TUI render correctly in desktop and IDE surfaces, and threads
created there replay correctly in the TUI, even when IDE context was
included.

Addresses https://github.com/openai/codex/issues/13834.

## What changed
### Summary
This PR consists of four four pieces:
1. An IPC client that uses a socket (Mac/Linux) or named pipe (Windows)
to talk to the IDE Extension
2. Logic that establishes the IPC connection and requests IDE context
(open files, selection) on demand
3. Logic that injects this context into the user prompt (using the same
technique as the desktop app) and hides the added context when rendering
the prompt in the TUI transcript
4. A new slash command for enabling/disabling this mode and text within
the footer to indicate when it's enabled

### Details
- Added `/ide [on|off|status]` to the TUI, with bare `/ide` toggling IDE
context on or off.
- Added a Rust IDE context client that connects to the local Codex IDE
IPC route as a client and requests context from the IDE extension flow.
- Injected IDE context using the same prompt delimiter and
transcript-stripping convention as the desktop app and IDE extension so
shared threads render consistently across surfaces.
- Added an `IDE context` status-line indicator while the feature is
active and cleared it when enabling or fetching context fails.
- Added handling for multiple selection ranges, oversized selections,
interleaved IPC messages, and transient reconnect timing after quick
toggles.

## Verification

Did extensive manual testing in addition to running automated unit and
regression tests.

To test:

- Launch VS Code (or Cursor) with the IDE extension.
- Open one or more files in the IDE and select a range of text within
one of them.
- Start the TUI.
- Ask the agent which files you have open in your IDE, and it should say
that it does not know.
- Enable `/ide` mode; note that `IDE context` appears in the lower
right.
- Ask the agent what files you have open in your IDE and what text is
selected.
2026-05-01 09:39:48 -07:00
Eric TrautandGitHub a93c89f497 Color TUI statusline from active theme (#19631)
## Why

Users have shared that the TUI can feel too visually flat because themes
mostly show up in code syntax highlighting. The configurable statusline
is a natural place to make the active theme more visible, while still
letting users keep the existing monotone statusline if they prefer it.

## What Changed

- Added a statusline styling helper that builds the rendered statusline
from `(StatusLineItem, text)` segments, preserving item identity while
keeping the plain text output unchanged.
- Derived foreground accent colors from the active syntax theme by
looking up TextMate scopes through the existing syntax highlighter, with
conservative ANSI fallbacks when a scope does not provide a foreground.
- Tuned theme-derived colors to keep the accents visible without making
the statusline feel overly bright.
- Added `[tui].status_line_use_colors`, defaulting to `true`, plus a
separated `/statusline` toggle so users can enable or disable
theme-derived statusline colors from the setup UI.
- Updated the live statusline and `/statusline` preview to use the same
styled builder, while keeping terminal-title preview text plain.
- Kept statusline separators and active-agent add-ons subdued while
removing blanket dimming from the whole passive statusline.

## Verification

- `cargo test -p codex-tui status_line`
- `cargo test -p codex-tui theme_picker`
- `cargo test -p codex-tui foreground_style_for_scopes`
- `cargo test -p codex-tui`
- `cargo test -p codex-config`
- `cargo test -p codex-core status_line_use_colors`
- `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`

## Visual

<img width="369" height="23" alt="Screenshot 2026-04-30 at 6 16 08 PM"
src="https://github.com/user-attachments/assets/11d03efb-8e4f-4450-8f4d-00a9659ef4cd"
/>

<img width="385" height="23" alt="Screenshot 2026-04-30 at 6 16 02 PM"
src="https://github.com/user-attachments/assets/a3d89f36-bdc1-42e8-8e84-61350e3999e2"
/>
2026-04-30 22:42:48 -07:00
Eric TrautandGitHub d898cc8f3f Format multi-day goal durations in the TUI (#20558)
## Why

Goal mode shows elapsed time in compact hour/minute form. That is easy
to scan for shorter runs, but once a goal runs past 24 hours, large hour
counts become harder to read at a glance.

## What changed

Updated `codex-rs/tui/src/goal_display.rs` so unbudgeted goal elapsed
time keeps the existing compact format below one day, then switches to a
day-aware format once the elapsed time reaches 24 hours:

- `23h 59m`
- `1d 0h 0m`
- `2d 23h 42m`

The formatter now covers the 24-hour boundary in unit tests, and the TUI
status-line snapshot for a completed elapsed goal now exercises the
multi-day display.

## Verification

- `cargo test -p codex-tui`

Here's my longest-running test task:

<img width="186" height="23" alt="image"
src="https://github.com/user-attachments/assets/cedfcdab-7f6e-44e6-8495-8a39f63973fb"
/>
2026-04-30 22:42:07 -07:00
xli-oaiandGitHub bb60b78c46 Surface admin-disabled remote plugin status (#20298)
## Summary

Remote plugin-service returns plugin availability separately from a
user's installed/enabled state. This adds `PluginAvailabilityStatus` to
the app-server protocol, propagates remote catalog `status` into
`PluginSummary`, and rejects install attempts for remote plugins marked
`DISABLED_BY_ADMIN` before downloading or caching the bundle.

This is the `openai/codex` half of the change. The companion
`openai/openai` webview PR is
https://github.com/openai/openai/pull/873269.

## Validation

- `cargo run -p codex-app-server-protocol --bin write_schema_fixtures`
- `cargo test -p codex-app-server --test all
plugin_list_marks_remote_plugin_disabled_by_admin`
- `cargo test -p codex-app-server --test all
plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
- `cargo test -p codex-app-server --test all
plugin_install_rejects_remote_plugin_disabled_by_admin_before_download`
- `cargo test -p codex-app-server-protocol schema_fixtures`
2026-04-30 20:00:07 -07:00
Felipe CouryandGitHub b6f81257f8 feat(tui): add vim composer mode (#18595)
## Why

Codex now has configurable TUI keymaps, but the composer still behaves
like a plain text field. Users who prefer modal editing need a way to
keep Vim muscle memory while drafting prompts, and the keymap picker
needs to expose Vim-specific actions if those bindings are configurable
instead of hardcoded.

## What Changed

- Adds composer Vim mode with insert/normal state, common normal-mode
movement and editing commands, `d`/`y` operator-pending flows, and
mode-aware footer and cursor indicators.
- Adds `/vim`, an optional global `toggle_vim_mode` binding, and
`tui.vim_mode_default` so Vim mode can be toggled per session or enabled
as the default composer state.
- Extends runtime and config keymaps with `vim_normal` and
`vim_operator` contexts, exposes those contexts in `/keymap`, refreshes
the config schema, and validates Vim bindings separately.
- Integrates Vim normal mode with existing composer behavior: `/` opens
slash command entry, `!` enters shell mode, `j`/`k` navigate history at
history boundaries, successful submissions reset back to normal mode,
and paste burst handling remains insert-mode only.
- Teaches the TUI render path to apply and restore cursor style so Vim
insert mode can use a bar cursor without leaving the terminal in that
state after exit.

## Validation

- `cargo test -p codex-tui keymap -- --nocapture` on the keymap/Vim
coverage
- `cargo insta pending-snapshots`

## Docs

This introduces user-facing `/vim`, `tui.vim_mode_default`, and Vim
keymap contexts under `tui.keymap`, so the public CLI configuration and
slash-command docs should be updated before the feature ships.
2026-04-30 17:20:51 -07:00
Celia ChenandGitHub 31f8813e3e fix: show correct Bedrock runtime endpoint in /status (#20275)
## Why

`/status` was showing the configured `ModelProviderInfo.base_url` for
Amazon Bedrock, which can be stale or misleading because the actual
Bedrock Mantle endpoint is derived at runtime from the resolved AWS
region. This made sessions report the wrong provider endpoint even
though requests used the correct runtime URL.

## What changed

- Added `ModelProvider::runtime_base_url()` so provider implementations
can expose the request-time base URL through the shared runtime provider
abstraction.
- Moved Bedrock region-to-Mantle URL resolution into
`amazon_bedrock::mantle::runtime_base_url()`, keeping region resolution
private to the Mantle module.
- Overrode `runtime_base_url()` for Amazon Bedrock so it returns the
resolved Mantle endpoint instead of the configured default.
- Resolved and cached the runtime provider base URL during TUI startup,
then used that cached value when rendering `/status`.
- Added status coverage that verifies Bedrock displays the runtime URL
and ignores the configured Bedrock `base_url` when they differ.

## Verification
model provider is resolved correctly in local build:
<img width="696" height="245" alt="Screenshot 2026-04-29 at 5 01 36 PM"
src="https://github.com/user-attachments/assets/a13c10a5-3720-41ab-8ace-3c4bc573f971"
/>
2026-04-30 19:02:34 +00:00
93d53f655b Add /hooks browser for lifecycle hooks (#19882)
## Why

`hooks/list` and `hooks/config/write` give us read/write access to hooks
and their state. This hooks up the TUI as a client so users can inspect
and manage that state directly.

## What

- add a two-page `/hooks` browser in the TUI: an event overview with
installed/active counts, followed by a per-event handler page with
toggle controls and detail rendering
- thread managed-state metadata through hook discovery and `hooks/list`
so the UI can label admin-managed hooks and suppress toggles for them
- persist hook toggles through the existing config-write path and add
snapshot coverage for the event list, handler list, managed-hook, and
empty states

## Stack

1. openai/codex#19705
2. openai/codex#19778
3. openai/codex#19840
4. This PR - openai/codex#19882

## Reviewer Notes

- Main UI logic is in
`codex-rs/tui/src/bottom_pane/hooks_browser_view.rs`; most of the diff
is the new view plus its snapshot coverage
- Request / write plumbing for opening the browser and persisting
toggles is in `codex-rs/tui/src/app/background_requests.rs` and
`codex-rs/tui/src/chatwidget/hooks.rs`
- Outside the TUI, the only behavioral change in this PR is threading
`is_managed` through hook discovery and `hooks/list` so managed hooks
render as non-toggleable
- The `codex-rs/tui/src/status/snapshots/` churn is unrelated merge
fallout from the stacked base branch's newer permission-label rendering

---------

Co-authored-by: Codex <noreply@openai.com>
2026-04-30 11:58:27 -07:00
Eric TrautandGitHub f2bc2f26a9 Remove core protocol dependency [2/2] (#20325)
## Why

With the local model layer and app-server routing in place from PR1,
this PR moves the active TUI runtime onto app-server notifications. The
affected pieces share the same event flow, so the command surface,
session state, bottom-pane prompts, chat rendering, history/status
views, and tests move together to keep the stacked branch buildable.

This PR also removes the obsolete compatibility surface that is no
longer used after the migration. The proposed protocol-boundary verifier
layer was dropped from the stack; enforcing that final boundary will be
simpler once `codex-tui` no longer needs any `codex_protocol`
references.

This PR is part 2 of a 2-PR stack:

1. Add TUI-owned replacement models and extract app-server event
routing.
2. Move the active TUI flow to app-server notifications and delete
obsolete adapter code.

## What changed

- Rewired app command and session handling to use app-server request and
notification shapes.
- Moved approval overlays, request-user-input flows, MCP elicitation,
realtime events, and review commands onto the app-server-facing model
surface.
- Updated chat rendering, history cells, status views, multi-agent UI,
replay state, and TUI tests to use app-server notifications plus the
local models introduced in PR1.
- Deleted `codex-rs/tui/src/app/app_server_adapter.rs` and the
superseded `chatwidget/tests/background_events.rs` fixture path.

## Verification

- `cargo check -p codex-tui --tests`
- Top of stack: `cargo test -p codex-tui`
2026-04-30 11:34:34 -07:00
canvrno-oaiandGitHub a85d265097 /plugins: remove marketplace (#19843)
This PR adds marketplace removal to the /plugins menu, giving users a
way to remove user-configured plugin marketplaces. It adds a `Ctrl+R`
shortcut to remove selected marketplace tabs, a confirmation prompt,
loading and error states, and the app-server request flow needed to
perform marketplace/remove. After a successful removal, the TUI
refreshes config, plugin mentions, user config, and plugin data so the
removed marketplace disappears from the menu and other surfaces in the
TUI.

- Add `Ctrl+R` removal option for user-configured marketplace tabs
- Show marketplace removal confirmation, loading, and error states
- Route `marketplace/remove` through the TUI background request flow
- Refresh config, plugin mentions, and plugin data after successful
removal
- Adds reusable per-tab footer hints so removal guidance only appears on
applicable tabs
- Add test coverage for `Ctrl+R` behavior while plugin search is active

Steps to test:
- Add a marketplace using the TUI /plugins menu
- Use Ctrl+R to remove the marketplace
- Accept the confirmation prompt
- Confirm the marketplace is removed when the process completes.
2026-04-30 10:25:07 -07:00
Michael BolinandGitHub ac4332c05b permissions: expose active profile metadata (#20095) 2026-04-29 20:54:59 -07:00
pakrym-oaiandGitHub fedcefe9da Reduce the surface of collaboration modes (#20149)
Collaboration modes were slightly invasive both into ThreadManager
construction and ModelProvider
2026-04-29 17:22:41 -07:00
Won ParkandGitHub 5cf0adba93 Include auto-review rollout in feedback uploads (#20064)
## Summary

- include the live auto-review trunk rollout when `/feedback` uploads
logs
- upload that attachment as
`auto-review-rollout-<parent-thread-id>.jsonl` so it is distinguishable
from the parent rollout
- show the same auto-review attachment name in the TUI consent popup

## Scope

- this only covers the live cached auto-review trunk for the current
parent thread
- it does not add durable historical parent->auto-review lookup
- it does not add persisted rollout support for ephemeral parallel
review forks

## UI 

<img width="599" height="185" alt="Screenshot 2026-04-28 at 1 17 18 PM"
src="https://github.com/user-attachments/assets/6a0e79c2-5d21-4702-8a89-f765778bc9e9"
/>

## Validation

- `cargo test -p codex-core
cached_guardian_subagent_exposes_its_rollout_path`
- `cargo test -p codex-feedback`
- `cargo test -p codex-app-server`
- `cargo test -p codex-tui feedback_upload_consent_popup_snapshot`
- `cargo test -p codex-tui
feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename`

## Known unrelated local failures

- `cargo test -p codex-core` currently fails in the pre-existing proxy
env snapshot test
`tools::runtimes::tests::maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive`
- `cargo test -p codex-tui` currently hits pre-existing `status::*`
snapshot drift unrelated to this change

## Follow-Up 
- persist parallel auto-review fork sessions so /feedback can include
their rollout history too
- attach each persisted fork as its own clearly named file, for example
auto-review-rollout-<parent-thread-id>-fork <n>.jsonl, instead of
merging multiple Guardian sessions into one attachment
- keep the same live-session-only scope initially; durable historical
parent -> auto-review lookup can remain a separate decision if we later
need feedback from resumed sessions
2026-04-29 11:44:55 -07:00
Eric TrautandGitHub d0204c3dcc TUI: Remove core protocol dependency [3/7] (#20174)
## Why

This is part 3 of a 7-PR stack to remove direct
`codex_protocol::protocol` usage from `codex-tui` while keeping each
layer reviewable and shippable.

With `AppCommand` now explicit, the internal app event bus can carry TUI
commands directly instead of bouncing through core `Op` values.

## What changed

- Changed `AppEvent::CodexOp` and `AppEvent::SubmitThreadOp` to carry
`AppCommand`.
- Updated app-event senders and direct emitters to submit `AppCommand`
values.
- Adjusted tests to match `AppCommand` or convert back through
`into_core()` where they intentionally assert legacy payload equality.

## Verification

- `cargo test -p codex-tui --no-run`
2026-04-29 10:52:10 -07:00
Eric TrautandGitHub 1c420a90cd TUI: Remove core protocol dependency [1/7] (#20172)
## Why

This is part 1 of a 7-PR stack to remove direct
`codex_protocol::protocol` usage from `codex-tui` while keeping each
layer reviewable and shippable.

This first layer reduces the size of the later `chatwidget` diff by
mechanically moving MCP startup bookkeeping out of the central widget
file without changing the event shapes or behavior.

## What changed

- Extracted MCP startup status handling into
`tui/src/chatwidget/mcp_startup.rs`.
- Kept the existing core event types in place for this purely mechanical
move.
- Updated the MCP startup tests to import the moved test-only event
types directly.

## Verification

- `cargo test -p codex-tui chatwidget::tests::mcp_startup`
2026-04-29 09:10:22 -07:00
Eric TrautandGitHub 91ca551df8 Use /goal resume for paused goals (#20082)
## Why

The paused goal statusline currently points users at `/goal` to unpause
a goal, but bare `/goal` is the summary command and does not change the
goal state. Instead of making `/goal` mutate state only when a goal is
paused, this gives the action an explicit command that reads naturally
in the UI.

## What Changed

- Replace `/goal unpause` with `/goal resume` for reactivating a paused
goal.
- Update the paused goal statusline and `/goal` summary copy to point at
`/goal resume`.
2026-04-29 08:56:02 -07:00
xl-openaiandGitHub 6f328d5e02 Soften skill description budget warnings (#20112)
Updates skill description budget messaging to be less alarming
2026-04-28 19:56:25 -07:00
canvrno-oaiandGitHub 24be9ac0a4 Restore TUI working status after steer message is set (#19939)
Fix for #19925

Restore the `Working` indicator after a streamed final answer finishes
when a user steer message is sent.
Add regression coverage for long output plus a mid-stream steer:
`cargo test -p codex-tui
final_answer_completion_restores_status_indicator_for_pending_steer`

Duplication/testing steps:
1. Start a new thread and ask for a long response.
2. While the response is streaming, submit a steer message.
3. When the first response finishes, observe whether `Working...` is
shown while waiting for the steer message response.
2026-04-28 18:10:40 -07:00
canvrno-oaiandGitHub 66b0781502 /plugins: add marketplace install flow (#18704)
This PR adds a new feature to the `/plugins` menu that gives users the
ability to add new plugin marketplaces. It introduces an Add Marketplace
tab to the right of installed marketplaces, a source prompt, loading and
error states, and the app-server request flow needed to perform the
install. After a successful `marketplace/add`, the popup refreshes back
into the newly added marketplace tab so the new plugins are immediately
visible.

- Add an Add Marketplace tab to the `/plugins` menu
- Prompt for marketplace source input from git repo, URL, or local path
- Show loading and error states during `marketplace/add`
- Refresh plugin data after success and switch into the newly added
marketplace tab
- Add tests and snapshot updates
2026-04-28 14:22:39 -07:00
Michael BolinandGitHub 3b74a4d3b1 tui: use permission profiles for sandbox state (#20008)
## Summary
- Move TUI permission state from legacy `SandboxPolicy` values to
canonical `PermissionProfile` values across presets, app events, chat
widget state, app commands, thread routing, and cached thread session
state.
- Keep app-server compatibility boundaries explicit: embedded sessions
send `permissionProfile`, while remote sessions send only a legacy
`sandbox` projection and fall back to read-only when a custom profile
cannot be projected.
- Update status/add-dir UI summaries and snapshots to render the active
permission profile, including workspace profiles selected by the new
built-in defaults.

## Verification
- `rg '\bSandboxPolicy\b' codex-rs/tui -n` returns no matches.
- `cargo test -p codex-tui`
- `cargo check -p codex-tui --tests`
- `cargo test -p codex-tui additional_dirs`
- `just fmt`
- `just fix -p codex-tui`




































---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20008).
* #20041
* #20040
* #20037
* #20035
* #20034
* #20033
* #20032
* #20030
* #20028
* #20027
* #20026
* #20024
* #20021
* #20018
* #20016
* #20015
* #20013
* #20011
* #20010
* __->__ #20008
2026-04-28 20:36:48 +00:00
640a1b23ea Fix plan mode nudge test after task completion signature change (#20045)
Updates the plan mode nudge test to pass the new `duration_ms` argument
to task completion.

Co-authored-by: Codex <noreply@openai.com>
2026-04-28 11:24:22 -07:00
Felipe CouryandGitHub c6bcd27832 feat(tui): suggest plan mode from composer drafts (#19901)
## Summary

- suggest Plan mode when the current composer draft contains the
standalone word `plan`
- shares the Codex App heuristics for detection
- excludes things line `/plan` and the word plan in shell mode
- reuse the existing `Shift+Tab` mode cycle and add thread-scoped
dismissal with `Esc`
- replace the normal footer hint while the reminder is visible so the
statusline stays anchored


https://github.com/user-attachments/assets/01123ae8-cee6-4e95-b563-44655c071cde

## Why

The desktop app already nudges users toward Plan mode when their draft
clearly signals planning intent. The TUI had the underlying `/plan` and
`Shift+Tab` flows, but no equivalent reminder at the moment the user was
most likely to benefit from them.

## Details

The reminder is shown only when Plan mode is available, the draft
contains standalone `plan`, the user is not already in Plan mode, the
composer is actionable, and the current thread has not dismissed the
reminder. Slash-command and shell-command drafts are excluded.

The first implementation used an extra composer row, but that moved the
statusline whenever the heuristic fired. This version keeps the layout
stable by rendering the reminder in the existing footer row instead.

## Validation

- `INSTA_UPDATE=always cargo test -p codex-tui
chatwidget::tests::plan_mode::plan_mode_nudge -- --nocapture`
- `just fmt`
- `just fix -p codex-tui`
- `./tools/argument-comment-lint/run.py -p codex-tui`
- `cargo insta pending-snapshots`
- `git diff --check`
2026-04-28 14:34:10 -03:00
Eric TrautandGitHub 087c9c1f1f TUI: use cumulative turn duration for worked-for separator (#19929)
## Why

Fixes #19814.

The TUI's current `Worked for ...` timing behavior is a leftover from
#9599. At that point, models could emit multiple assistant messages in
one turn for preambles/commentary, but the TUI did not yet have a
reliable signal that an assistant message was the final answer when it
started streaming. To avoid showing an ever-growing elapsed time on each
preamble separator, #9599 made the separator timer incremental by
tracking elapsed time since the previous separator.

That workaround is no longer the right model for the final
completed-turn display. Since then, #16638 added protocol-native turn
timing, including `duration_ms` on turn completion. With that cumulative
duration available at the point where the TUI renders the completed-turn
separator, the UI can show the actual turn duration directly instead of
carrying per-separator timing state.

## What Changed

- Thread `duration_ms` into `ChatWidget::on_task_complete` from both
legacy `TurnCompleteEvent` handling and app-server `TurnCompleted`
notifications.
- Use `duration_ms` for the final `Worked for ...` separator, falling
back to the status indicator timer only when the protocol duration is
unavailable.
- Keep mid-turn separators before later assistant text as plain visual
dividers instead of clocked `Worked for ...` separators.
- Remove the old incremental separator timer state and helper
(`last_separator_elapsed_secs` / `worked_elapsed_from`).
- Add a snapshot regression test for a turn that runs a command and then
completes with a final answer, verifying the final separator uses the
cumulative turn duration.

## Verification

- `cargo test -p codex-tui
final_worked_for_uses_cumulative_turn_duration_snapshot`
- `just fix -p codex-tui`

Manual repro prompt:

```text
Manual timing repro. First send a short preamble/commentary sentence before using tools. Then run exactly this shell command: sleep 75; echo MANUAL_TIMING_DONE. After the command finishes, give a final answer that says "done". Do not skip the preamble.
```

After this change, the mid-turn break before the final answer should be
a plain divider, and the final completed-turn separator should show
`Worked for ...` using the cumulative turn duration.

Before:
<img width="414" height="102" alt="Screenshot 2026-04-27 at 10 09 01 PM"
src="https://github.com/user-attachments/assets/b9e2ce01-2460-40e4-a5c4-c9ba8add2557"
/>


After:
<img width="485" height="149" alt="Screenshot 2026-04-27 at 10 09 07 PM"
src="https://github.com/user-attachments/assets/d24089ae-d4e2-41b6-b966-07c98706ead4"
/>
2026-04-28 09:24:29 -07:00
Felipe CouryandGitHub 5e737372ee feat(tui): add configurable keymap support (#18593)
## Why

The TUI currently handles keyboard shortcuts as hard-coded event matches
spread across app, composer, pager, list, approval, and navigation code.
That makes shortcuts hard to customize, makes displayed hints easy to
drift from actual behavior, and makes future keymap work riskier because
there is no central action inventory.

This PR adds the foundation for configurable, action-based keymaps
without adding the interactive remapping UI yet. Onboarding
intentionally stays on fixed startup shortcuts because users cannot
reasonably configure keymaps before completing onboarding.

This is PR1 in the keymap stack:

- PR1: #18593: configurable keymap foundation
- PR2: #18594: `/keymap` picker and guided remapping UI
- PR3: #18595: Vim composer mode and the remap option

## Design Notes

The new model resolves named actions into concrete runtime bindings once
from config, then passes those bindings to the UI surfaces that handle
input or render shortcut hints.

The main concepts are:

- **Context**: a scope where an action is active, such as `global`,
`chat`, `composer`, `editor`, `pager`, `list`, or `approval`.
- **Action**: a named operation inside a context, such as
`global.open_transcript`, `composer.submit`, or `pager.close`.
- **Binding**: one or more single-key shortcuts assigned to an action,
written as config strings such as `ctrl-t`, `alt-backspace`, or
`page-down`. Multi-step sequences such as `ctrl-x ctrl-s`, `g g`, or
leader-key flows are not part of this PR.
- **Resolution order**: context-specific config wins first, supported
global fallbacks come next, and built-in defaults fill in anything
unset.
- **Explicit unbinding**: an empty array removes an action binding in
that scope and does not fall through to a fallback binding.
- **Conflict validation**: a resolved keymap rejects duplicate active
bindings inside the same scope so one keypress cannot dispatch two
actions.

## What Changed

- Added `TuiKeymap` config support under `[tui.keymap]`, including typed
contexts/actions, key alias normalization, generated schema coverage,
and user-facing config errors.
- Added `RuntimeKeymap` resolution in `codex-rs/tui/src/keymap.rs`,
including fallback precedence, built-in defaults, explicit unbinding,
and per-context conflict validation.
- Rewired existing TUI handlers to consume resolved keymap actions
instead of directly matching hard-coded keys in each component.
- Updated key hint rendering and footer/pager/list surfaces so displayed
shortcuts follow the resolved keymap.
- Kept onboarding shortcuts fixed in
`codex-rs/tui/src/onboarding/keys.rs` instead of exposing them through
`[tui.keymap]`.

## Validation

The branch includes focused coverage for config parsing, key
normalization, runtime fallback resolution, explicit unbinding,
duplicate-key conflict validation, default keymap consistency,
onboarding startup key behavior, and UI hint snapshots affected by
resolved key bindings.
2026-04-28 12:52:25 -03:00
Michael BolinandGitHub bf38def44e permissions: make SessionConfigured profile-only (#19774)
## Why

`SessionConfiguredEvent` is the internal event that tells clients what
permissions are active for a session. Emitting both `sandbox_policy` and
`permission_profile` leaves two possible authorities and forces every
consumer to decide which one to honor. At this point in the migration,
the profile is expressive enough to represent managed, disabled, and
external sandbox enforcement, so the internal event can be profile-only.

The wire compatibility concern is older serialized events or rollout
data that only contain `sandbox_policy`; those still need to
deserialize.

## What Changed

- Removes `sandbox_policy` from `SessionConfiguredEvent` and makes
`permission_profile` required.
- Adds custom deserialization so old payloads with only `sandbox_policy`
are upgraded to a cwd-anchored `PermissionProfile`.
- Updates core event emission and TUI session handling to sync
permissions from the profile directly.
- Updates app-server response construction to derive the legacy
`sandbox` response field from the active thread snapshot instead of from
`SessionConfiguredEvent`.
- Updates yolo-mode display logic to treat both
`PermissionProfile::Disabled` and managed unrestricted filesystem plus
enabled network as full-access, while still preserving the distinction
between no sandbox and external sandboxing.

## Verification

- `cargo test -p codex-protocol session_configured_event --lib`
- `cargo test -p codex-protocol serialize_event --lib`
- `cargo test -p codex-exec session_configured --lib`
- `cargo test -p codex-app-server
thread_response_permission_profile_preserves_enforcement --lib`
- `cargo test -p codex-core
session_configured_reports_permission_profile_for_external_sandbox
--lib`
- `cargo test -p codex-tui session_configured --lib`
- `cargo test -p codex-tui
yolo_mode_includes_managed_full_access_profiles --lib`


































---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19774).
* #19900
* #19899
* #19776
* #19775
* __->__ #19774
2026-04-27 22:06:47 -07:00
pakrym-oaiandGitHub 4e05f3053c Remove ghost snapshots (#19481)
## Summary
- Remove `ghost_snapshot` / `GhostCommit` from the Responses API surface
and generated SDK/schema artifacts.
- Keep legacy config loading compatible, but make undo a no-op that
reports the feature is unavailable.
- Clean up core history, compaction, telemetry, rollout, and tests to
stop carrying ghost snapshot items.

## Testing
- Unit tests passed for `codex-protocol`, `codex-core` targeted undo and
compaction flows, `codex-rollout`, and `codex-app-server-protocol`.
- Regenerated config and app-server schemas plus Python SDK artifacts
and verified they match the checked-in outputs.
2026-04-27 18:48:57 -07:00
canvrno-oaiandGitHub e64c765673 Show action required in terminal title (#18372)
Implements #18162

This updates the TUI terminal title to show an explicit action-required
state when Codex is blocked on user approval or input. The terminal
title now uses the activity title item to cover both active work and
blocked-on-user states, while still accepting the legacy spinner config
value.

Changes
- Rename the terminal title item from `spinner` to `activity` while
preserving legacy config compatibility
- Show `[ ! ] Action Required `while approval or input overlays are
active, with a blinking `[ . ]` alternate state
- Suppress the normal working spinner while Codex is blocked on user
action
- Add targeted coverage for action-required title behavior and legacy
title-item parsing

Testing
- Trigger an approval or input modal and confirm the tab title
alternates between `[ ! ] Action Required` and `[ . ] Action Required`
- Disable the activity title item and confirm the action-required title
does not appear
- Resolve the prompt and confirm the title returns to the normal
spinning/idel state


https://github.com/user-attachments/assets/e9ecc530-a6be-4fd7-b9a6-d550a790eb2c
2026-04-27 15:27:11 -07:00
Eric TrautandGitHub 48dd7b58f0 Render delegated patch approval details (#19709)
## Why

Fixes #19632.

When a delegated agent requests approval for an in-progress file change,
the parent TUI handles that request from an inactive thread. The app
server already sent the `FileChange` item with the proposed diff, but
the inactive-thread approval path was not recovering and rendering it
the same way as the active-thread path.

The result was an inconsistent approval prompt: main-thread edits show a
normal patch preview history item before the approval modal, while
delegated edits did not show that preview in the transcript flow.

## What Changed

- Recover buffered or historical `FileChange` item changes when building
inactive-thread file-change approval requests.
- Reuse the app-server file-change conversion helper for both live
transcript rendering and inactive-thread approvals.
- Render recovered delegated patches as a normal patch preview history
cell before the approval modal.
- Keep apply-patch approval modals focused on the decision prompt and
optional metadata; they do not render a synthetic command line or embed
the diff body.

## Manual Repro And Verification

I manually reproduced the issue using a file under `~/Desktop` so the
write would require approval.

Before the fix:

1. Ask the main thread: `Use apply_patch, not shell redirection or
Python, to create ~/Desktop/bug1.txt with three short lines.`
2. Observe the expected TUI shape: the transcript shows a normal patch
preview such as `• Added ~/Desktop/bug1.txt (+N -0)` above the approval
modal, and the modal contains only the approval prompt/options without a
synthetic command line.
3. Ask for the delegated path: `Spawn a worker. Have it use apply_patch,
not shell redirection or Python, to create ~/Desktop/bug1.txt with four
short lines.`
4. Observe the delegated approval is inconsistent: the parent view does
not render the proposed patch as the normal transcript preview before
the modal, so the diff context is missing from the stream or appears
inside the modal instead of in the history flow.

After the fix:

1. Repeat the delegated worker prompt with `apply_patch`.
2. Confirm the parent view renders the same normal patch preview history
cell (`• Added ~/Desktop/bug1.txt (+N -0)` plus the diff) immediately
before the approval modal.
3. Confirm the approval modal remains focused on the decision prompt.
For delegated approvals it may show the worker thread label, but it
should not show a `$ apply_patch` command line or embed the diff body in
the modal.
2026-04-27 10:07:15 -07:00
Eric TrautandGitHub 0e2300c02c Persist shell mode commands in prompt history (#19618)
## Why

`!` shell commands are currently surfaced as "Bash mode", which is
misleading for users running shells such as PowerShell or zsh. Those
commands also bypass the persistent prompt history path, so they cannot
be recalled after starting a new session.

Fixes #19613.

## What changed

- Rename the TUI footer label and related test wording from "Bash mode"
to "Shell mode".
- Persist accepted `!` shell commands to prompt history with the leading
`!`, so recall restores the composer into shell mode across sessions.
- Add coverage for immediate and queued shell-command submissions
emitting the prompt-history update.

## Verification

- `cargo test -p codex-tui bang_shell`
- `cargo test -p codex-tui shell_command_uses_shell_accent_style`
- `cargo test -p codex-tui footer_mode_snapshots`
- `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`

Manually verified fix after confirming presence of bug prior to fix.
2026-04-27 09:54:25 -07:00
Michael BolinandGitHub 0ccd659b4b permissions: store only constrained permission profiles (#19735) 2026-04-26 20:59:58 -07:00
Won ParkandGitHub 8033b6a449 Add /auto-review-denials retry approval flow (#19058)
## Why

Auto-review can deny an action that the user later decides they want to
retry. Today there is no TUI surface for selecting a recent denial and
sending explicit approval context back into the session, so users have
to restate intent manually and the retry can be reviewed without the
original denied action context.

This adds a narrow TUI-driven path for approving a recent denied action
while still keeping the retry inside the normal auto-review flow.

## What Changed

- Added `/auto-review-denials` to open a picker of recent denied
auto-review actions.
- Added a small in-memory TUI store for the 10 most recent denied
auto-review events.
- Selecting a denial sends the structured denied event back through the
existing core/app-server op path.
- Core now injects a developer message containing the approved action
JSON rather than the full assessment event.
- Auto-review transcript collection now preserves this specific approval
developer message so follow-up review sessions can see the user approval
context.
- Added TUI snapshot/unit coverage for the picker and approval dispatch
path.
- Added core coverage for retaining the approval developer message in
the auto-review transcript.

## Verification

- `cargo test -p codex-core
collect_guardian_transcript_entries_keeps_manual_approval_developer_message`
- `cargo test -p codex-tui auto_review_denials`
- `cargo test -p codex-tui
approving_recent_denial_emits_structured_core_op_once`

## Notes

This intentionally keeps retries going through auto-review. The approval
signal is context for the exact previously denied action, not a blanket
bypass for similar future actions.
2026-04-27 03:43:53 +00:00
Michael BolinandGitHub 0d8cdc0510 permissions: centralize legacy sandbox projection (#19734)
## Why

The remaining migration work still needs `SandboxPolicy` at a few
compatibility boundaries, but those projections should come from one
canonical path. Keeping ad hoc legacy projections scattered through
app-server, CLI, and config code makes it easy for behavior to drift as
`PermissionProfile` gains fidelity that the legacy enum cannot
represent.

## What Changed

- Adds `Permissions::legacy_sandbox_policy(cwd)` and
`Config::legacy_sandbox_policy()` as the compatibility projection from
the canonical `PermissionProfile`.
- Adds `Permissions::can_set_legacy_sandbox_policy()` so legacy inputs
are checked after they are converted into profile semantics.
- Updates app-server command handling, Windows sandbox setup, session
configuration, and sandbox summaries to use the centralized projection
helper.
- Leaves `SandboxPolicy` in place only for boundary inputs/outputs that
still speak the legacy abstraction.

## Verification

- `cargo check -p codex-config -p codex-core -p codex-sandboxing -p
codex-app-server -p codex-cli -p codex-tui`
- `cargo test -p codex-tui
permissions_selection_history_snapshot_full_access_to_default --
--nocapture`
- `cargo test -p codex-tui
permissions_selection_sends_approvals_reviewer_in_override_turn_context
-- --nocapture`
- `bazel test //codex-rs/tui:tui-unit-tests-bin
--test_arg=permissions_selection_history_snapshot_full_access_to_default
--test_output=errors`
- `bazel test //codex-rs/tui:tui-unit-tests-bin
--test_arg=permissions_selection_sends_approvals_reviewer_in_override_turn_context
--test_output=errors`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19734).
* #19737
* #19736
* #19735
* __->__ #19734
2026-04-26 20:31:23 -07:00
Michael BolinandGitHub ad57a3fee2 permissions: finish profile-backed app surfaces (#19395) 2026-04-26 19:42:39 -07:00
Michael BolinandGitHub deaa307fb2 permissions: derive compatibility policies from profiles (#19392)
## Why

After #19391, `PermissionProfile` and the split filesystem/network
policies could still be stored in parallel. That creates drift risk: a
profile can preserve deny globs, external enforcement, or split
filesystem entries while a cached projection silently loses those
details. This PR makes the profile the runtime source and derives
compatibility views from it.

## What Changed

- Removes stored filesystem/network sandbox projections from
`Permissions` and `SessionConfiguration`; their accessors now derive
from the canonical `PermissionProfile`.
- Derives legacy `SandboxPolicy` snapshots from profiles only where an
older API still needs that field.
- Updates MCP connection and elicitation state to track
`PermissionProfile` instead of `SandboxPolicy` for auto-approval
decisions.
- Adds semantic filesystem-policy comparison so cwd changes can preserve
richer profiles while still recognizing equivalent legacy projections
independent of entry ordering.
- Updates config/session tests to assert profile-derived projections
instead of parallel stored fields.

## Verification

- `cargo test -p codex-core direct_write_roots`
- `cargo test -p codex-core runtime_roots_to_legacy_projection`
- `cargo test -p codex-app-server
requested_permissions_trust_project_uses_permission_profile_intent`



































































---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392).
* #19395
* #19394
* #19393
* __->__ #19392
2026-04-26 15:06:42 -07:00
Michael BolinandGitHub 4d7ce3447d permissions: make runtime config profile-backed (#19606)
## Why

This supersedes #19391. During stack repair, GitHub marked #19391 as
merged into a temporary stack branch rather than into `main`, so the
runtime-config change needed a fresh PR.

`PermissionProfile` is now the canonical permissions shape after #19231
because it can distinguish `Managed`, `Disabled`, and `External`
enforcement while also carrying filesystem rules that legacy
`SandboxPolicy` cannot represent cleanly. Core config and session state
still needed to accept profile-backed permissions without forcing every
profile through the strict legacy bridge, which rejected valid runtime
profiles such as direct write roots.

The unrelated CI/test hardening that previously rode along with this PR
has been split into #19683 so this PR stays focused on the permissions
model migration.

## What Changed

- Adds `Permissions.permission_profile` and
`SessionConfiguration.permission_profile` as constrained runtime state,
while keeping `sandbox_policy` as a legacy compatibility projection.
- Introduces profile setters that keep `PermissionProfile`, split
filesystem/network policies, and legacy `SandboxPolicy` projections
synchronized.
- Uses a compatibility projection for requirement checks and legacy
consumers instead of rejecting profiles that cannot round-trip through
`SandboxPolicy` exactly.
- Updates config loading, config overrides, session updates, turn
context plumbing, prompt permission text, sandbox tags, and exec request
construction to carry profile-backed runtime permissions.
- Preserves configured deny-read entries and `glob_scan_max_depth` when
command/session profiles are narrowed.
- Adds `PermissionProfile::read_only()` and
`PermissionProfile::workspace_write()` presets that match legacy
defaults.

## Verification

- `cargo test -p codex-core direct_write_roots`
- `cargo test -p codex-core runtime_roots_to_legacy_projection`
- `cargo test -p codex-app-server
requested_permissions_trust_project_uses_permission_profile_intent`




---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19606).
* #19395
* #19394
* #19393
* #19392
* __->__ #19606
2026-04-26 13:29:54 -07:00
Eric TrautandGitHub f1c963d77e Add goal TUI UX (5 / 5) (#18077)
Adds the TUI user experience for goals on top of the core runtime from
PR 4.

## Why

Users need a direct TUI control surface for long-running goals. The UI
should make the current goal visible, support common goal actions
without waiting for a model turn, and avoid confusing end-of-turn
notifications while an active goal is immediately continuing.

## What changed

- Added `/goal` summary rendering for the current goal, including
active, paused, budget-limited, and complete states.
- Added `/goal <objective>` creation/replacement through the app-server
goal API rather than a model prompt.
- Added `/goal clear`, `/goal pause`, and `/goal unpause` command
variants.
- Added a confirmation menu when the user enters a new goal while
another goal already exists.
- Updated `/goal` help and summary tip text so it reflects the supported
command variants without advertising slash-command token budgets.
- Added footer/statusline goal indicators, including elapsed time and
token budget display when a budget exists from API/tool-created goals.
- Consumes goal updated/cleared notifications so the TUI stays in sync
with external app-server changes.
- Suppresses end-of-turn desktop notifications only when a goal is still
active and follow-up work is expected.
- Preserves slash-command history behavior and avoids leaking queued
`/goal` state into unrelated submissions.

## Verification

- Added TUI unit and snapshot coverage for goal command availability,
summary rendering, control commands, replacement menu behavior,
status/footer display, notification handling, and command history.
2026-04-24 21:16:45 -07:00
Curtis 'Fjord' HawthorneandGitHub 8a559e7938 Remove js_repl feature (#19410) 2026-04-24 17:49:29 -07:00
Michael BolinandGitHub 789f387982 permissions: remove legacy read-only access modes (#19449)
## Why

`ReadOnlyAccess` was a transitional legacy shape on `SandboxPolicy`:
`FullAccess` meant the historical read-only/workspace-write modes could
read the full filesystem, while `Restricted` tried to carry partial
readable roots. The partial-read model now belongs in
`FileSystemSandboxPolicy` and `PermissionProfile`, so keeping it on
`SandboxPolicy` makes every legacy projection reintroduce lossy
read-root bookkeeping and creates unnecessary noise in the rest of the
permissions migration.

This PR makes the legacy policy model narrower and explicit:
`SandboxPolicy::ReadOnly` and `SandboxPolicy::WorkspaceWrite` represent
the old full-read sandbox modes only. Split readable roots, deny-read
globs, and platform-default/minimal read behavior stay in the runtime
permissions model.

## What changed

- Removes `ReadOnlyAccess` from
`codex_protocol::protocol::SandboxPolicy`, including the generated
`access` and `readOnlyAccess` API fields.
- Updates legacy policy/profile conversions so restricted filesystem
reads are represented only by `FileSystemSandboxPolicy` /
`PermissionProfile` entries.
- Keeps app-server v2 compatible with legacy `fullAccess` read-access
payloads by accepting and ignoring that no-op shape, while rejecting
legacy `restricted` read-access payloads instead of silently widening
them to full-read legacy policies.
- Carries Windows sandbox platform-default read behavior with an
explicit override flag instead of depending on
`ReadOnlyAccess::Restricted`.
- Refreshes generated app-server schema/types and updates tests/docs for
the simplified legacy policy shape.

## Verification

- `cargo check -p codex-app-server-protocol --tests`
- `cargo check -p codex-windows-sandbox --tests`
- `cargo test -p codex-app-server-protocol sandbox_policy_`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19449).
* #19395
* #19394
* #19393
* #19392
* #19391
* __->__ #19449
2026-04-24 17:16:58 -07:00
Michael BolinandGitHub 13e0ec1614 permissions: make legacy profile conversion cwd-free (#19414)
## Why

The profile conversion path still required a `cwd` even when it was only
translating a legacy `SandboxPolicy` into a `PermissionProfile`. That
made profile producers invent an ambient `cwd`, which is exactly the
anchoring we are trying to remove from permission-profile data. A legacy
workspace-write policy can be represented symbolically instead: `:cwd =
write` plus read-only `:project_roots` metadata subpaths.

This PR creates that cwd-free base so the rest of the stack can stop
threading cwd through profile construction. Callers that actually need a
concrete runtime filesystem policy for a specific cwd still have an
explicitly named cwd-bound conversion.

## What Changed

- `PermissionProfile::from_legacy_sandbox_policy` now takes only
`&SandboxPolicy`.
- `FileSystemSandboxPolicy::from_legacy_sandbox_policy` is now the
symbolic, cwd-free projection for profiles.
- The old concrete projection is retained as
`FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd` for
runtime/boundary code that must materialize legacy cwd behavior.
- Workspace-write profiles preserve `CurrentWorkingDirectory` and
`ProjectRoots` special entries instead of materializing cwd into
absolute paths.

## Verification

- `cargo check -p codex-protocol -p codex-core -p
codex-app-server-protocol -p codex-app-server -p codex-exec -p
codex-exec-server -p codex-tui -p codex-sandboxing -p
codex-linux-sandbox -p codex-analytics --tests`
- `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
-p codex-app-server -p codex-exec -p codex-exec-server -p codex-tui -p
codex-sandboxing -p codex-linux-sandbox -p codex-analytics`




---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19414).
* #19395
* #19394
* #19393
* #19392
* #19391
* __->__ #19414
2026-04-24 13:42:05 -07:00
7262c0c450 Skip disabled rows in selection menu numbering and default focus (#19170)
Selection menus in the TUI currently let disabled rows interfere with
numbering and default focus. This makes mixed menus harder to read and
can land selection on rows that are not actionable. This change updates
the shared selection-menu behavior in list_selection_view so disabled
rows are not selected when these views open, and prevents them from
being numbered like selectable rows.

- Disabled rows no longer receive numeric labels
- Digit shortcuts map to enabled rows only
- Default selection moves to the first enabled row in mixed menus
- Updated affected snapshot
- Added snapshot coverage for a plugin detail error popup
- Added a focused unit test for shared selection-view behavior

---------

Co-authored-by: Codex <noreply@openai.com>
2026-04-24 13:21:43 -07:00