mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
ebd3d534516ad0597ae38cd550fc755eb530e4cb
1757 Commits
-
feat(tui): render responsive Markdown tables in TUI (#22052)
## Why The TUI currently treats Markdown tables as ordinary wrapped text, which makes table-heavy responses hard to read and brittle across narrow panes and terminal resizes. This change teaches the TUI to render Markdown tables responsively while preserving the raw Markdown source needed to re-render streamed and finalized transcript content after width changes. The goal is to keep tables legible during streaming, after resize, and once a turn has finished, without corrupting scrollback ordering. ## What Changed - add table detection and responsive table rendering in the Markdown renderer - render standard tables with Unicode box-drawing borders when the pane is wide enough - add a vertical readability fallback for constrained or dense tables so narrow panes still show each row clearly - keep links and `<br>` content inside table cells instead of leaking text outside the table - avoid table normalization inside fenced or indented code blocks - preserve raw streamed Markdown source and keep the active table as a mutable tail until finalization - consolidate finalized streamed content into source-backed transcript cells so post-resize re-rendering stays correct - add snapshot and targeted streaming/resize regression coverage for the new table behavior ## How to Test 1. Start Codex TUI from this branch. 2. Paste this exact prompt: `This is a session to test codex, no need to do any thinking, just end different markdown tables, with columns exploring different markdown contents, like links, bold italic, code, etc. Make them different sizes, some 30+ rows, some not and intertwine them with some paragraphs with complex formatting as well.` 3. Confirm the response includes several Markdown tables mixed with richly formatted paragraphs. 4. Confirm wide-enough tables render with box-drawing borders instead of plain wrapped pipe text. 5. Resize the terminal narrower while the answer is still streaming and confirm the in-progress table stays coherent instead of duplicating headers or leaving broken scrollback behind. 6. Resize again after the turn finishes and confirm the finalized transcript re-renders cleanly at the new width. 7. In a narrow pane, verify dense tables fall back to the vertical per-row layout instead of producing unreadable wrapped columns. 8. Also verify pipe-heavy fenced code blocks still render as code, not as tables. Targeted tests: - `cargo test -p codex-tui table_readability_fallback --no-fail-fast` - `cargo test -p codex-tui markdown_render --no-fail-fast` - `cargo test -p codex-tui streaming::controller --no-fail-fast` - `cargo test -p codex-tui table_resize_lifecycle --no-fail-fast` ## Docs No developer docs update appears necessary.
Felipe Coury ·
2026-05-10 20:42:11 +00:00 -
fix(tui): preserve Shift+Enter in tmux csi-u panes (#21943)
## Why Inside tmux, `Shift+Enter` can still reach Codex as a plain `Enter` even when tmux has extended keys enabled. In `csi-u` tmux panes, Codex needs to request `modifyOtherKeys` mode 2 so tmux moves the pane from `VT10x` into extended-key mode and preserves the Shift modifier. Without that extra request, composer `Shift+Enter` submits the draft instead of inserting a newline. Fixes #21699. ## What Changed - Detect tmux sessions and read the active `extended-keys-format`, preferring the pane-local value before falling back to the global option. - Request `modifyOtherKeys` mode 2 for tmux panes using `csi-u` extended keys, and reset it when restoring keyboard reporting. - Add unit coverage for tmux detection, the format gate, and the emitted `modifyOtherKeys` escape sequence. ## How to Test 1. In tmux, configure: ```tmux set-option -g extended-keys on set-option -g extended-keys-format csi-u ``` 2. Start Codex in a fresh tmux pane from this branch. 3. From another pane, confirm the Codex pane reports `mode=Ext 2`: ```bash tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} mode=#{pane_key_mode} cmd=#{pane_current_command}' ``` 4. Type a draft in the composer and press `Shift+Enter`; confirm it inserts a newline instead of submitting. 5. Also confirm plain `Enter` still submits as before. Targeted tests: - `cargo test -p codex-tui` ## Notes - Manual verification used both real `Shift+Enter` in iTerm2/tmux and `tmux send-keys ... S-Enter` to confirm the tmux pane changes from `VT10x` to `Ext 2` and preserves newline behavior. - On this checkout, the broader `codex-tui` test run currently reaches unrelated existing failures in `status::tests::*` plus a later stack overflow in `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
Felipe Coury ·
2026-05-10 11:45:49 -03:00 -
Split ChatWidget state into focused modules (#21866)
## Summary `ChatWidget` has been carrying several independent domains in one large state bag: transcript bookkeeping, turn lifecycle, queued input, status surfaces, connectors, review mode, and protocol dispatch. That makes otherwise-local changes hard to reason about because unrelated fields and side effects live beside each other in `chatwidget.rs`. This is the first cleanup PR in a larger decomposition effort. It does not try to make `chatwidget.rs` small in one sweep; instead, it establishes focused state boundaries that later handler, popup, rendering, and effect-synchronization extractions can build on. This PR keeps `ChatWidget` as the composition layer while moving focused state into smaller `codex-tui` modules. The widget still owns effects that touch the bottom pane, app events, command submission, redraw scheduling, and terminal-title updates. ## Changes - Add focused state modules under `codex-rs/tui/src/chatwidget/` for input queues, turn lifecycle, transcript bookkeeping, status state, connectors, review mode, and app-server protocol dispatch. - Update `ChatWidget` to hold grouped state structs and route input/lifecycle/status operations through those focused helpers. - Move app-server notification dispatch into `chatwidget/protocol.rs` while leaving feature handlers and side effects on `ChatWidget`. - Replace the large manual `ChatWidget` test literal with the normal constructor plus narrow test overrides, so future state moves do not require every field to be restated in test setup. - Update existing tests to access the new grouped state or narrower helpers without changing snapshot behavior. ## Longer-term direction Follow-up PRs can continue shrinking `chatwidget.rs` by moving behavior, not just state, into focused modules: - Extract input/submission flow, turn/stream handling, and tool-cell lifecycles into domain modules that call the new state reducers. - Move popup/settings builders and rendering helpers out of the main widget file so `ChatWidget` stays focused on composition. - Reduce direct `BottomPane` mutation by applying domain-specific sync outputs at clearer boundaries.
Eric Traut ·
2026-05-09 15:16:01 -07:00 -
Avoid blocking TUI on agent metadata hydration (#21870)
## Why Fixes #16688. The TUI currently hydrates collab receiver metadata by awaiting `thread/read` before each active-thread notification is rendered. During large subagent fan-outs, the embedded app-server can be busy starting agents and processing spawn work, so those synchronous metadata reads queue behind the fan-out and block the TUI event loop. That makes the UI appear frozen even though the underlying agent work can continue. ## What Changed - Replaced eager `thread/read` metadata hydration on the active notification path with local receiver-thread caching. - Kept `ThreadStarted` and picker refreshes as the places that fill in agent nickname/role metadata when it is available. - Skipped caching receiver threads that are explicitly reported as `NotFound`, avoiding live-looking ghost entries for failed stale-agent calls. - Added TUI tests covering both local receiver caching and `NotFound` suppression. ## Verification - `cargo test -p codex-tui collab_receiver_notification` - `just fix -p codex-tui` I also ran the full `cargo test -p codex-tui`; the new test passed, but the full process later aborted with an unrelated stack overflow in `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
Eric Traut ·
2026-05-09 15:15:40 -07:00 -
Improve hooks trust flow in TUI (#21755)
# Why Hooks that need trust review were easy to miss, and the existing TUI flow made users discover `/hooks` manually before they could decide whether to inspect or trust them. # What - add a startup review prompt for new or changed hooks before normal composer use - add a top-level `t` shortcut in `/hooks` to trust every review-needed hook at once - make pending-review rows and helper copy use warning styling ## TUI ### Startup review interstitial ```text Hooks need review 2 hooks are new or changed. Hooks can run outside the sandbox after you trust them. › 1. Review hooks 2. Trust all and continue 3. Continue without trusting (hooks won't run) ``` ### Top-level `/hooks` page when review is needed ```text Hooks Lifecycle hooks from config and enabled plugins. ⚠ 1 hook needs review before it can run. Event Installed Active Review Description PreToolUse 1 0 1 Before a tool executes ... Press t to trust all; enter to review hooks; esc to close ```
Abhinav ·
2026-05-09 21:17:30 +00:00 -
fix(tui): improve light-mode selection contrast (#21950)
## Why On light terminal backgrounds, selected rows in several TUI pickers were rendered with the same bright cyan accent used on dark themes. Against the light menu surface, that made the current selection hard to distinguish at a glance. <table><tr> <td> <p align="center">Before</p> <img width="1109" height="864" alt="SCR-20260509-nmtz" src="https://github.com/user-attachments/assets/b31ce0d0-19c2-4bdd-a220-7acc77bd8e8e" /> </td> <td> <p align="center">After</p> <img width="1164" height="844" alt="SCR-20260509-nmox" src="https://github.com/user-attachments/assets/7b3fede0-4739-4a9f-a979-cdbb7451841f" /> </td> </tr></table> ## What changed - Added a shared background-aware accent style for active/selected TUI controls. - Use a darker cyan-family accent on light backgrounds while preserving the existing bright cyan accent on dark or unknown backgrounds. - Reused that accent across shared picker rows and the custom selection-like surfaces that had drifted separately: picker tabs, hooks browsing, external-agent migration choices, and /keymap affordances. - Added focused tests for the light/dark accent rule and rendered selected-row styling. ## How to Test 1. Start Codex in a terminal using a light background theme. 2. Type `/` to open the slash-command picker and move the selection through a few rows. 3. Confirm that the selected row is visibly colored with strong contrast instead of blending into the popup surface. 4. Open `/keymap` and confirm the active tab, selected rows, and picker hint accents use the same light-theme accent treatment. 5. In a dark terminal theme, repeat the slash-picker check and confirm the existing bright cyan selection styling is preserved. Targeted tests: - `cargo test -p codex-tui accent_style_uses_` - `cargo test -p codex-tui selected_rows_use_the_shared_accent_style` - `cargo test -p codex-tui selected_event_rows_use_the_shared_accent_style` Notes: - A full `cargo test -p codex-tui` run reached the end of the suite but hit an unrelated existing stack overflow in `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
Felipe Coury ·
2026-05-09 16:10:56 -03:00 -
fix(tui): preserve wrapped prose beside URLs (#21760)
## Why Mixed prose lines that contained URLs started taking the URL-preserving wrapping path, but that path could split ordinary words mid-token. A follow-up issue remained in scrollback insertion: when already-rendered indented rows were wrapped again, continuation rows could lose their margin and fall back to terminal hard wrapping. Together those bugs made normal Markdown output look broken around links, lists, blockquotes, and indented content. Separately, the local argument-comment lint wrappers failed under environments that set `PYTHONSAFEPATH=1`, because Python no longer adds the script directory to `sys.path` automatically. That prevented the lint from reaching Rust callsites at all. <img width="1778" height="1558" alt="CleanShot 2026-05-09 at 11 51 38" src="https://github.com/user-attachments/assets/9274d150-1757-4f1a-89ac-5bdc9997d8cb" /> ## What Changed - Preserve URL tokens without turning every neighboring prose word into a character-level split point. - Add a mixed URL/prose wrapper that keeps ordinary words whole, preserves leading whitespace, and re-splits long non-URL tokens against the actual width available on continuation rows. - Reuse a rendered history row's leading whitespace as the continuation indent when scrollback insertion has to pre-wrap it again. - Add regression coverage for markdown wrapping, history-cell rendering, scrollback continuation margins, leading-indent width accounting, and continuation-row re-splitting. - Make both argument-comment lint entrypoints explicitly add their own directory to `sys.path`, so sibling imports still work when `PYTHONSAFEPATH=1`. ## How to Test 1. Start Codex and render a long Markdown response that mixes prose with inline links, blockquotes, lists, and indented code-like text. 2. Confirm that ordinary words next to links stay whole instead of breaking mid-word. 3. Resize or replay the transcript and confirm wrapped continuation rows keep their expected left margin for blockquotes, lists, and indented content. 4. Run the source argument-comment lint from a shell with `PYTHONSAFEPATH=1` and confirm it starts normally instead of failing to import `wrapper_common`. Targeted tests: - `cargo test -p codex-tui mixed_line --lib` - `cargo test -p codex-tui preserves_prefix_on_wrapped_rows --lib` - `cargo test -p codex-tui agent_markdown_cell_does_not_split_words_after_inline_markdown --lib` - `cargo test -p codex-tui mixed_url_markdown_wraps_prose_without_splitting_words_snapshot --lib` - `python3 tools/argument-comment-lint/test_wrapper_common.py` - `just argument-comment-lint-from-source -p codex-tui -- --lib` Notes: - `cargo test -p codex-tui` currently reaches the new tests successfully, then still aborts in the pre-existing `tests::fork_last_filters_latest_session_by_cwd_unless_show_all` stack-overflow failure.
Felipe Coury ·
2026-05-09 13:58:10 -03:00 -
[codex] Lowercase TUI service tier commands (#21906)
## Why Service-tier slash commands are built from model-catalog metadata. If the catalog returns a name like `Fast`, the TUI currently exposes `/Fast` and exact dispatch expects that casing, which is inconsistent with the lowercase command style used elsewhere. ## What - Lowercase service-tier command names when converting catalog tiers into `ServiceTierCommand` values. - Add regression coverage that seeds a catalog tier named `Fast` and expects the generated command to be `fast`. ## Testing Not run locally per repo instruction; PR CI should run the new `service_tier_commands_lowercase_catalog_names` coverage.
Ahmed Ibrahim ·
2026-05-09 14:29:12 +03:00 -
[codex] request desktop attestation from app (#20619)
## Summary TL;DR: teaches `codex-rs` / app-server to request a desktop-provided attestation token and attach it as `x-oai-attestation` on the scoped ChatGPT Codex request paths.  ## Details This PR teaches the Codex app-server runtime how to request and attach an attestation token. It does not generate DeviceCheck tokens directly; instead, it relies on the connected desktop app to advertise that it can generate attestation and then asks that app for a fresh header value when needed. The flow is: 1. The Codex desktop app connects to app-server. 2. During `initialize`, the app can advertise that it supports `requestAttestation`. 3. Before app-server calls selected ChatGPT Codex endpoints, it sends the internal server request `attestation/generate` to the app. 4. app-server receives a pre-encoded header value back. 5. app-server forwards that value as `x-oai-attestation` on the scoped outbound requests. The code in this repo is mostly protocol and runtime plumbing: it adds the app-server request/response shape, introduces an attestation provider in core, wires that provider into Responses / compaction / realtime setup paths, and covers the intended scoping with tests. The signed macOS DeviceCheck generation remains owned by the desktop app PR. ## Related PR - Codex desktop app implementation: https://github.com/openai/openai/pull/878649 ## Validation <details> <summary>Tests run</summary> ```sh cargo test -p codex-app-server-protocol cargo test -p codex-core attestation --lib cargo test -p codex-app-server --lib attestation ``` Also ran: ```sh just fix -p codex-core just fix -p codex-app-server just fix -p codex-app-server-protocol just fmt just write-app-server-schema ``` </details> <details> <summary>E2E DeviceCheck validation</summary> First validated the signed desktop app boundary directly: launched a packaged signed `Codex.app`, sent `attestation/generate`, decoded the returned `v1.` attestation header, and validated the extracted DeviceCheck token with `personal/jm/verify_devicecheck_token.py` using bundle ID `com.openai.codex`. Apple returned `status_code: 200` and `is_ok: true`. Then ran the fuller app + app-server flow. The packaged `Codex.app` launched a current-branch app-server via `CODEX_CLI_PATH`, and a local MITM proxy intercepted outbound `chatgpt.com` traffic. The app-server requested `attestation/generate` from the real Electron app process, and the intercepted `/backend-api/codex/responses` traffic included `x-oai-attestation` on both routes: ```text GET /backend-api/codex/responses Upgrade: websocket x-oai-attestation: present POST /backend-api/codex/responses Upgrade: none x-oai-attestation: present ``` The captured header decoded to a DeviceCheck token that also validated with Apple for `com.openai.codex` (`status_code: 200`, `is_ok: true`, team `2DC432GLL2`). </details> --------- Co-authored-by: Codex <noreply@openai.com>
Jiaming Zhang ·
2026-05-08 12:36:02 -07:00 -
Load configured environments from CODEX_HOME (#20667)
## Why The earlier PRs add stdio transport support and the config-backed environment provider, but the feature remains inert until normal Codex entrypoints construct `EnvironmentManager` with enough context to discover `CODEX_HOME/environments.toml`. This final stack PR activates the provider while preserving the legacy `CODEX_EXEC_SERVER_URL` fallback when no environments file exists. **Stack position:** this is PR 5 of 5. It is the product wiring PR that activates the configured environment provider added in PR 4. ## What Changed - Thread `codex_home` into `EnvironmentManagerArgs`. - Change `EnvironmentManager::new(...)` to load the provider from `CODEX_HOME`. - Preserve legacy behavior by falling back to `DefaultEnvironmentProvider::from_env()` when `environments.toml` is absent. - Make `environments.toml`-backed managers start new threads with all configured environments, default first, while keeping the legacy env-var path single-default. - Update the app-server, TUI, exec, MCP server, connector, prompt-debug, and thread-manager-sample callsites to pass `codex_home` and handle provider-loading errors. ## Self-Review Notes - The multi-environment startup path is intentionally tied to the `environments.toml` provider. Using `>1` configured environment as the only signal would also expand the legacy `CODEX_EXEC_SERVER_URL` provider because it keeps `local` addressable alongside `remote`. - The startup environment list is still derived inside `EnvironmentManager`; the provider only says whether its snapshot should start new threads with all configured environments. - The thread-manager sample was updated to pass the current `ThreadManager::new(...)` installation id argument so the stack compiles under Bazel. ## Stack - 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server listener - 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server client transport - 3. https://github.com/openai/codex/pull/20665 - Make environment providers own default selection - 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME environments TOML provider - **5. This PR:** https://github.com/openai/codex/pull/20667 - Load configured environments from CODEX_HOME Split from original draft: https://github.com/openai/codex/pull/20508 ## Validation - `just fmt` - `git diff --check` - `bazel build --config=remote --strategy=remote --remote_download_toplevel //codex-rs/thread-manager-sample:codex-thread-manager-sample` - `bazel test --config=remote --strategy=remote --remote_download_toplevel //codex-rs/exec-server:exec-server-unit-tests` - `bazel test --config=remote --strategy=remote --remote_download_toplevel --test_sharding_strategy=disabled --test_arg=default_thread_environment_selections_use_manager_default_id //codex-rs/core:core-unit-tests` - `bazel test --config=remote --strategy=remote --remote_download_toplevel --test_sharding_strategy=disabled --test_arg=start_thread_uses_all_default_environments_from_codex_home //codex-rs/core:core-unit-tests` ## Documentation This activates `CODEX_HOME/environments.toml`; user-facing documentation should be added before this stack is treated as a documented public workflow. --------- Co-authored-by: Codex <noreply@openai.com>
starr-openai ·
2026-05-08 11:17:56 -07:00 -
[codex] Generalize service tier slash commands (#21745)
## Why `/fast` was wired as a one-off slash command even though model metadata now exposes service tiers as catalog data. That meant adding another tier, such as a slower/cheaper tier, would require more hardcoded TUI plumbing instead of letting the model catalog drive the available commands. This change makes service-tier commands data-driven: each advertised `service_tiers` entry becomes a `/name` command using the catalog description, while the request path sends the tier `id` only when the selected model supports it. ## What Changed - Removed the hardcoded `/fast` slash-command variant and introduced dynamic service-tier command items in the composer and command popup. - Added toggle behavior for service-tier commands: invoking `/name` selects that tier, and invoking it again clears the selection. - Preserved the existing Fast-mode keybinding/status affordances by resolving the current model tier whose name is `fast`, while still sending the tier request value such as `priority`. - Persisted service-tier selections as raw request strings so non-fast tiers can round-trip through config. - Updated the Bedrock catalog entry to advertise fast support through `service_tiers` with `id: "priority"` and `name: "fast"`. - Added defensive filtering in core so unsupported selected service tiers are omitted from `/responses` requests. ## Validation - Added/updated coverage for dynamic service-tier slash command lookup, popup descriptions, composer dispatch, TUI fast toggling, and unsupported-tier omission in core request construction. - Local tests were not run per request. --------- Co-authored-by: Codex <noreply@openai.com>
Ahmed Ibrahim ·
2026-05-08 20:09:51 +03:00 -
jif-oai ·
2026-05-08 17:53:23 +02:00 -
Show permissions and approval mode in the TUI status line (#21677)
Fixes #21665. ## Why The TUI status line is the right place for compact, glanceable session state. The original request was motivated by the need to see the active permission posture without opening `/permissions` or `/status`, especially when switching between safer and more permissive modes during a session. This PR intentionally separates `permissions` from `approval-mode` instead of combining them into one status-line item. They answer related but different questions: `permissions` describes the active sandbox/profile shape, while `approval-mode` describes how command approvals are handled. Keeping them separate makes each item independently configurable and avoids long combined labels in an already space-constrained status line. The tradeoff is that users who want the full permission posture in the status line need to opt into both items. In exchange, users can show only the sandbox/profile label, only the approval behavior, or both, and named user-defined profiles remain concise. Non-standard permission shapes are rendered as `Custom permissions` rather than trying to squeeze detailed profile contents into the status line; `/status` remains the fuller explanatory surface. ## What changed - Added a configurable `permissions` status-line item. - Added a separate `approval-mode` status-line item, with `approval` as an alias. - Render standard permission states compactly as `Read Only`, `Workspace`, or `Full Access`. - Preserve user-defined permission profile names directly in the status line. - Render unnamed non-standard permission shapes as `Custom permissions`. - Refresh status surfaces when `/permissions` updates the permission profile, approval policy, or approval reviewer. - Updated status-line preview snapshot coverage for the new items. ## Verification - `cargo test -p codex-tui status_permissions_non_default_workspace_write_uses_workspace_label` - `cargo test -p codex-tui permissions_selection_emits_history_cell_when_selection_changes` - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
Eric Traut ·
2026-05-08 08:03:11 -07:00 -
Display blended token count in status line (#21669)
## Why The configurable `/statusline` and terminal title can display session token usage. That display was using the raw total token count, which includes cached input tokens, so it significantly overstated the token usage compared with the blended token count shown elsewhere (in `/status` and tracked in goals). This inconsistency resulted in user confusion. We don't want to report cached tokens because we don't charge for them and they are somewhat of an implementation detail that users shouldn't care about. ## What changed - Use `TokenUsage::blended_total()` for the `used-tokens` status surface item so cached input is excluded. - Add a brief comment to `tokens_in_context_window()` clarifying that it returns raw `total_tokens`, whose meaning depends on whether the caller has last-turn or accumulated usage.
Eric Traut ·
2026-05-08 07:56:13 -07:00 -
Update models.json (#19896)
Automated update of models.json. --------- Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com> Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
github-actions[bot] ·
2026-05-08 17:41:55 +03:00 -
Remove skills list extra roots (#21485)
## Summary - Remove `perCwdExtraUserRoots` / `SkillsListExtraRootsForCwd` from the `skills/list` app-server API. - Drop Rust app-server and `codex-core-skills` extra-root plumbing so skill scans are keyed by the normal cwd/user/plugin roots only. - Regenerate app-server schemas and update docs/tests that only existed for the removed extra-roots behavior. ## Validation - `just write-app-server-schema` - `just fmt` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-core-skills` - `just fix -p codex-app-server-protocol` - `just fix -p codex-core-skills` - `just fix -p codex-app-server` - `just fix -p codex-tui` ## Notes - `cargo test -p codex-app-server --test all skills_list` ran the edited skills-list cases, but the full filtered run ended on existing `skills_changed_notification_is_emitted_after_skill_change` timeout after a websocket `401`. - `cargo test -p codex-tui --lib` compiled the changed TUI callers, then failed two unrelated status permission tests because local `/etc/codex/requirements.toml` forbids `DangerFullAccess`. - Source-truth check found the OpenAI monorepo still has generated/app-server-kit mirror references to the removed field; those should be cleaned up when generated app-server types are synced or in a companion OpenAI cleanup.
xli-oai ·
2026-05-07 20:56:42 -07:00 -
[codex-analytics] plumb protocol-native review timing (#21434)
## Why We want terminal tool review analytics, but the reducer should not stamp review timing from its own wall clock. This PR plumbs review timing through the real protocol and app-server seams so downstream analytics can consume the emitter's timestamps directly. Guardian reviews keep their enriched `started_at` / `completed_at` analytics fields by deriving those legacy second-based values from the same protocol-native millisecond lifecycle timestamps, rather than sampling a separate analytics clock. ## What changed - add `started_at_ms` to user approval request payloads - add `started_at_ms` / `completed_at_ms` to guardian review notifications - preserve Guardian review `started_at` / `completed_at` enrichment from the protocol-native timing source - stamp typed `ServerResponse` analytics facts with app-server-observed `completed_at_ms` - thread the new timing fields through core, protocol, app-server, TUI, and analytics fixtures ## Verification - `cargo test -p codex-app-server outgoing_message --manifest-path codex-rs/Cargo.toml` - `cargo test -p codex-app-server-protocol guardian --manifest-path codex-rs/Cargo.toml` - `cargo test -p codex-tui guardian --manifest-path codex-rs/Cargo.toml` - `cargo test -p codex-analytics analytics_client_tests --manifest-path codex-rs/Cargo.toml` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21434). * #18748 * __->__ #21434 * #18747 * #17090 * #17089 * #20514
rhan-oai ·
2026-05-07 20:31:41 -07:00 -
Show plugin hooks in plugin details (#21447)
Supersedes the abandoned #19859, rebuilt on latest `main`. # Why PR #19705 adds discovery for hooks bundled with plugins, but `/plugins` still only shows skills, apps, and MCP servers. This follow-up makes bundled hooks visible in the same plugin detail view so users can inspect the full plugin surface in one place. We also need `PluginHookSummary` to populate Plugin Hooks in the app; `hooks/list` is not enough there because plugin detail needs to show hooks for disabled plugins too. # What - extend `plugin/read` with `PluginHookSummary` entries for bundled hooks - summarize plugin hooks while loading plugin details - render a `Hooks` row in the `/plugins` detail popup <img width="3456" height="848" alt="CleanShot 2026-04-27 at 11 45 34@2x" src="https://github.com/user-attachments/assets/fe3a38d6-a260-4351-8513-fb04c93d725b" />
Abhinav ·
2026-05-07 00:21:14 -07:00 -
Revert state DB injection and agent graph store (#21481)
## Why Reverts #20689 to restore the previous optional state DB plumbing. The conflict resolution keeps the newer installation ID and session/thread identity changes that landed after #20689, while removing the mandatory state DB and agent graph store dependency from ThreadManager construction. ## What changed - Restored `Option<StateDbHandle>` through app-server, MCP server, prompt debug, and test entry points. - Removed the `codex-core` dependency on `codex-agent-graph-store` and reverted descendant lookup back to the existing state DB path when available. - Kept newer `installation_id` forwarding by passing it beside the optional DB handle. - Kept local thread-name updates working when the optional state DB handle is absent. ## Validation - `git diff --check` - `cargo test -p codex-thread-store` - `cargo test -p codex-state -p codex-rollout -p codex-app-server-protocol` - Attempted `env CARGO_INCREMENTAL=0 cargo test -p codex-core -p codex-app-server -p codex-app-server-client -p codex-mcp-server -p codex-thread-manager-sample -p codex-tui`; blocked locally by a rustc ICE while compiling `v8 v146.4.0` with `rustc 1.93.0 (254b59607 2026-01-19)` on `aarch64-apple-darwin`.
pakrym-oai ·
2026-05-06 22:48:29 -07:00 -
fix(tui): clear first inline viewport render (#21450)
## Why The alpha TUI can render the initial trust-directory prompt with stale terminal text showing through spaces when startup begins below existing shell output. The first inline viewport transition can happen while the previous viewport is still empty, so the old clear path no-ops before Ratatui draws the prompt. Ratatui then skips blank cells because its previous buffer also thinks those cells are blank, leaving old terminal contents visible inside the prompt. ## What Changed - Clear from the new inline viewport top when the previous viewport is empty during a viewport transition. - Keep the existing clear-from-old-viewport behavior for normal viewport updates. - Add a VT100-backed regression test that pre-fills terminal contents, performs the first viewport clear, and verifies stale text inside the new viewport is removed while shell content above the viewport remains. ## How to Test 1. Start Codex alpha in a terminal that already has visible shell output above the cursor. 2. Use a fresh untrusted project directory so the trust-directory prompt appears. 3. Confirm the prompt text renders cleanly, with spaces staying blank instead of showing fragments of previous shell output. 4. As a regression check, confirm content above the inline viewport is still preserved in terminal scrollback. Targeted tests: - `cargo test -p codex-tui first_viewport_change_clears_from_new_viewport_when_old_viewport_is_empty -- --nocapture` - `cargo test -p codex-tui`
Felipe Coury ·
2026-05-07 02:48:49 +00:00 -
Add compact lifecycle hooks (started by vincentkoc - external contrib) (#19905)
Based on work from Vincent K - https://github.com/openai/codex/pull/19060 <img width="1836" height="642" alt="CleanShot 2026-04-29 at 20 47 40@2x" src="https://github.com/user-attachments/assets/b647bb89-65fe-40c8-80b0-7a6b7c984634" /> ## Why Compaction rewrites the conversation context that future model turns receive, but hooks currently have no deterministic lifecycle point around that rewrite. This adds compact lifecycle hooks so users can audit manual and automatic compaction, surface hook messages in the UI, and run post-compaction follow-up without overloading tool or prompt hooks. ## What Changed - Added `PreCompact` and `PostCompact` hook events across hook config, discovery, dispatch, generated schemas, app-server notifications, analytics, and TUI hook rendering. - Added trigger matching for compact hooks with the documented `manual` and `auto` matcher values. - Wired `PreCompact` before both local and remote compaction, and `PostCompact` after successful local or remote compaction. - Kept compact hook command input to lifecycle metadata: session id, Codex turn id, transcript path, cwd, hook event name, model, and trigger. - Made compact stdout handling consistent with other hooks: plain stdout is ignored as debug output, while malformed JSON-looking stdout is reported as failed hook output. - Added integration coverage for compact hook dispatch, trigger matching, post-compact execution, and the audited behavior that `decision:"block"` does not block compaction. ## Out of Scope - Hook-specific compaction blocking is not implemented; `decision:"block"` and exit-code-2 blocking semantics are intentionally unsupported for `PreCompact`. - Custom compaction instructions are not exposed to compact hooks in this PR. - Compact summaries, summary character counts, and summary previews are not exposed to compact hooks in this PR. ## Verification - `cargo test -p codex-hooks` - `cargo test -p codex-core manual_pre_compact_block_decision_does_not_block_compaction` - `cargo test -p codex-app-server hooks_list` - `cargo test -p codex-core config_schema_matches_fixture` - `cargo test -p codex-tui hooks_browser` ## Docs The developer documentation for Codex hooks should be updated alongside this feature to document `PreCompact` and `PostCompact`, the `manual`/`auto` matcher values, and the compact hook payload fields. --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Andrei Eternal ·
2026-05-06 18:08:31 -07:00 -
feat: Add marketplace source filtering and plugin share context (#21419)
Adds marketplaceKinds to plugin/list for local, workspace-directory, and shared-with-me; omitted params keep default local plus gated global behavior, while explicit kinds are exact. Exposes shareContext on plugin summaries from local share mappings and remote workspace/shared responses, including remotePluginId and nullable creator metadata. Adds shared-with-me listing through /ps/plugins/workspace/shared, renames the workspace remote namespace to workspace-directory, and keeps direct remote read/share/install/update/delete paths gated by plugins rather than remote_plugin.
xl-openai ·
2026-05-06 16:12:23 -07:00 -
[codex-tui] pass thread source for tui threads (#21401)
## Summary - mark TUI-created thread starts and forks with explicit `thread_source = user` - add focused coverage for embedded and remote lifecycle request builders ## Why Thread analytics now consume an explicit thread-level source classification instead of inferring it from `session_source`. The TUI still omitted that field, so TUI-created interactive threads would continue to land as `null` even after the new analytics plumbing shipped. ## Validation - `cargo test -p codex-tui app_server_session --lib`
rhan-oai ·
2026-05-06 13:18:41 -07:00 -
[codex] Dedupe fallback model metadata warnings (#21090)
Fixes #21070. This is a small cleanup around model metadata handling for gateway/provider model names. It follows the report and proposed direction from @dkbush by keeping the fallback metadata warning useful without repeating it every turn, and by tightening the existing provider-prefix lookup path. - Track fallback metadata warning slugs in session state so each unresolved model warns once per session. - Keep warning emission outside the session-state lock and preserve the existing warning text. - Allow one-segment provider prefixes with hyphenated provider IDs, while preserving the multi-segment rejection behavior. - Add focused coverage for warning dedupe and hyphenated provider-prefix metadata matching. Testing: - Ran `just fmt`. - Ran `git diff --check`. - Added tests for the new warning dedupe and provider-prefix lookup behavior.
canvrno-oai ·
2026-05-06 13:11:44 -07:00 -
Route opted-in MCP elicitations through Guardian (#19431)
# Motivation Browser Use origin-access prompts are MCP elicitations, not direct tool-call approval prompts, so they were bypassing the Guardian approval path. We need a generic opt-in that lets eligible MCP elicitations use Guardian when the current turn already routes approvals there. # Description Add a generic elicitation reviewer hook in codex-mcp and wire codex-core to pass a Guardian reviewer callback when creating the MCP connection manager. The reviewer validates explicit mcp_tool_call opt-in metadata, builds a Guardian MCP tool-call review request from server/tool/connector metadata and tool params, and maps Guardian approval, denial, timeout, and cancellation decisions back to MCP elicitation responses. The new option to trigger this in the `_meta` object is: ``` "codex_request_type": "approval_request", ``` # Testing - RUST_MIN_STACK=8388608 NEXTEST_STATUS_LEVEL=leak cargo nextest run --no-fail-fast --cargo-profile ci-test --test-threads 2 - cargo clippy --tests -- -D warnings - cargo fmt -- --config imports_granularity=Item --check - cargo shear - pnpm run format - python3 .github/scripts/verify_cargo_workspace_manifests.py - python3 .github/scripts/verify_tui_core_boundary.py - python3 .github/scripts/verify_bazel_clippy_lints.py - git diff --check
Clark DuVall ·
2026-05-06 19:42:45 +00:00 -
fix(tui): persist ctrl-c draft via app event (#21397)
## Why The main branch started failing after #21351 merged because the merge commit kept calling `AppCommand::add_to_history` from `BottomPane::clear_composer_for_ctrl_c`, but main had already removed that helper as part of the history persistence refactor. The PR head passed because it was based on an older main commit where the helper still existed. This restores the Ctrl+C draft-stashing behavior using the current app-event path instead of the removed command helper. ## What Changed - Store the active `ThreadId` in `BottomPane` when history metadata is provided. - Emit `AppEvent::AppendMessageHistoryEntry` for Ctrl+C-cleared drafts. - Update the slash-clear regression test to assert the current history event shape. ## How to Test Targeted tests: - `cargo test -p codex-tui slash_clear_after_ctrl_c_keeps_stashed_draft_recallable` Broader local checks: - `just fix -p codex-tui` - `just argument-comment-lint -p codex-tui` - `git diff --check origin/main...HEAD` - `cargo test -p codex-tui` reached completion; the fixed test passed, and the only local failures were `status::tests::status_permissions_full_disk_managed_*`, blocked by this machine config rejecting `DangerFullAccess` via `/etc/codex/requirements.toml`.
Felipe Coury ·
2026-05-06 19:03:11 +00:00 -
fix(tui): keep Ctrl-C stashed drafts after /clear (#21351)
## Why When a user stashes a draft with Ctrl+C, then runs `/clear`, the fresh chat session loses the in-memory composer history that held the stashed draft. Pressing Up after `/clear` can then recall an older submitted prompt instead of the draft the user explicitly saved for later. ## What Changed - Record Ctrl+C-cleared composer text through the existing message history path, so it survives the fresh session created by `/clear`. - Keep `/clear` itself out of local slash-command recall so it does not sit ahead of the stashed draft. - Add regression coverage for the full flow: submit a prompt, stash a later draft with Ctrl+C, run `/clear`, then recall the stashed draft before the older prompt. ## How to Test 1. Start Codex with `just c`. 2. Submit a short prompt such as `ok` and wait for the turn to complete. 3. Type a new draft, press Ctrl+C, then run `/clear`. 4. Press Up and confirm the stashed draft is restored. 5. Press Up again and confirm the older submitted prompt is still reachable after the stashed draft. Targeted tests: - `cargo test -p codex-tui slash_clear_after_ctrl_c_keeps_stashed_draft_recallable` Manual verification: - Reproduced the issue in tmux with `RUST_LOG=trace just c -c log_dir=...`: before the fix, Up after `/clear` recalled the older submitted prompt. - Re-tested the same tmux flow after the fix: Up after `/clear` restored the Ctrl+C-stashed draft.
Felipe Coury ·
2026-05-06 14:46:18 -03:00 -
Move message history out of core (#21278)
## Why Message history was implemented inside `codex-core` and surfaced through core protocol ops and `SessionConfiguredEvent` fields even though the current consumer is TUI-local prompt recall. That made core own UI history persistence and exposed `history_log_id` / `history_entry_count` through surfaces that app-server and other clients do not need. This change moves message history persistence out of core and keeps the recall plumbing local to the TUI. ## What changed - Added a new `codex-message-history` crate for appending, looking up, trimming, and reading metadata from `history.jsonl`. - Removed core protocol history ops/events: `AddToHistory`, `GetHistoryEntryRequest`, and `GetHistoryEntryResponse`. - Removed `history_log_id` and `history_entry_count` from `SessionConfiguredEvent` and updated exec/MCP/test fixtures accordingly. - Updated the TUI to dispatch local app events for message-history append/lookup and keep its persistent-history metadata in TUI session state. ## Validation - `cargo test -p codex-message-history -p codex-protocol` - `cargo test -p codex-exec event_processor_with_json_output` - `cargo test -p codex-mcp-server outgoing_message` - `cargo test -p codex-tui` - `just fix -p codex-message-history -p codex-protocol -p codex-core -p codex-tui -p codex-exec -p codex-mcp-server`
pakrym-oai ·
2026-05-06 08:35:42 -07:00 -
2- Use string service tiers in session protocol (#20971)
## Summary - break service tier session/op/app-server protocol fields from the closed enum to string tier ids - send the service tier string directly through model requests, prewarm, compaction, memories, and TUI/app-server turn starts - regenerate app-server protocol JSON/TypeScript schemas, removing the standalone ServiceTier TS enum ## Verification - just fmt - cargo check -p codex-core -p codex-app-server -p codex-tui - just write-app-server-schema --------- Co-authored-by: Codex <noreply@openai.com>
Ahmed Ibrahim ·
2026-05-06 18:00:21 +03:00 -
feat(app-server): move v2
sessionIdontoThread(#21336)## Why `session_id` and `thread_id` are separate identities after #20437, but app-server only surfaced `sessionId` on the `thread/start`, `thread/resume`, and `thread/fork` response envelopes. Other thread-bearing surfaces such as `thread/list`, `thread/read`, `thread/started`, `thread/rollback`, `thread/metadata/update`, and `thread/unarchive` either lacked the grouping key or forced clients to special-case those three responses. Making `sessionId` part of the reusable `Thread` payload gives every v2 API surface one place to expose session-tree identity. ## Mental model 1. thread.sessionId lives on `Thread` 2. It is a view/runtime identity for the current live session tree, not durable stored lineage metadata 3. When app-server has a live loaded thread, it copies the real value from core’s session_configured.session_id 4. When it only has stored/unloaded data, it falls back to thread.sessionId = thread.id ## What changed - Added `sessionId` to the v2 [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109). - Removed the duplicate top-level `sessionId` fields from `thread/start`, `thread/resume`, and `thread/fork`; clients should now read `response.thread.sessionId`. - Populated `thread.sessionId` when building live thread responses, replaying loaded threads, and returning stored-thread summaries so the field is present across start, resume, fork, list, read, rollback, metadata-update, unarchive, and `thread/started` paths. See [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918) and [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719). - Preserved the stored-thread fallback: if a thread has not been loaded into a live session tree yet, `thread.sessionId` falls back to `thread.id`; once the thread is live again, the field reports the active session tree root. - Regenerated the JSON/TypeScript schemas and updated the app-server README examples to show [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310) on the thread object.
jif-oai ·
2026-05-06 15:23:25 +02:00 -
feat: add
session_id(#20437)## Summary Related to https://openai.slack.com/archives/C095U48JNL9/p1777537279707449 TLDR: We update the meaning of session ids and thread ids: * thread_id stays as now * session_id become a shared id between every thread under a /root thread (i.e. every sub-agent share the same session id) This PR introduces an explicit `SessionId` and threads it through the protocol/client boundary so `session_id` and `thread_id` can diverge when they need to, while preserving compatibility for older serialized `session_configured` events. --------- Co-authored-by: Codex <noreply@openai.com>
jif-oai ·
2026-05-06 10:48:37 +02:00 -
Support Codex Apps auth elicitations (#19193)
## Summary - request URL-mode MCP elicitations when Codex Apps tool calls fail with connector auth metadata - route Codex Apps auth URL elicitations into the TUI app-link flow ## Test plan - `just fmt` - `cargo test -p codex-core mcp_tool_call::tests` - `cargo test -p codex-mcp` - `cargo test -p codex-tui bottom_pane::app_link_view::tests` - `just fix -p codex-core` - `just fix -p codex-mcp` - `just fix -p codex-tui` Also attempted broader local runs: - `cargo test -p codex-core` fails in unrelated config/request-permission/proxy-sensitive tests under the current Codex Desktop environment. - `cargo test -p codex-tui` fails in unrelated status snapshots/trust-default tests because the ambient environment renders workspace-write/network permission defaults.
Matthew Zeng ·
2026-05-06 07:18:00 +00:00 -
[codex-analytics] rework thread_source for thread analytics (#20949)
## Summary - make `thread_source` an explicit optional thread-level field on `thread/start`, `thread/fork`, and returned thread payloads - persist `thread_source` in rollout/session metadata so resumed live threads retain the original value - replace the old best-effort `session_source` -> `thread_source` mapping with an explicit caller-supplied analytics classification ## Why Before this change, analytics `thread_source` was populated by a best-effort mapping from `session_source`. `session_source` describes the runtime/client surface, not the actual thread-level origin, so that projection was not accurate enough to distinguish cases such as `user`, `subagent`, `memory_consolidation`, and future thread origins reliably. Making `thread_source` explicit keeps one thread-level analytics field while letting callers provide the real classification directly instead of recovering it indirectly from `session_source`. ## Impact For new analytics events, `thread_source` now reflects the explicit thread-level classification supplied by the caller rather than an inferred value derived from `session_source`. Existing protocol fields remain optional; callers that omit `threadSource` now produce `null` instead of a best-effort inferred value. ## Validation - `just write-app-server-schema` - `cargo test -p codex-analytics -p codex-core -p codex-app-server-protocol --no-run` - `cargo test -p codex-app-server-protocol generated_ts_optional_nullable_fields_only_in_params` - `cargo test -p codex-analytics thread_initialized_event_serializes_expected_shape` - `cargo test -p codex-core resume_stopped_thread_from_rollout_preserves_thread_source`
rhan-oai ·
2026-05-06 02:12:31 +00:00 -
Expose plugin manifest keywords in app server (#21271)
## Summary - Add plugin manifest keywords to core plugin marketplace/detail models - Expose keywords on app-server v2 PluginSummary and generated schema/types - Populate keywords in plugin/list and plugin/read responses for local plugins Depends on https://github.com/openai/openai/pull/891087 ## Validation - just fmt - just write-app-server-schema - cargo test -p codex-app-server-protocol - cargo test -p codex-core-plugins - cargo test -p codex-app-server plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load - cargo test -p codex-app-server plugin_read_returns_plugin_details_with_bundle_contents
Abdulrahman Alfozan ·
2026-05-06 02:09:05 +00:00 -
fix build (#21261)
I believe a merge race in https://github.com/openai/codex/pull/20689 broke the build, so this is a quick fix. `cargo check --tests` passed locally.
Michael Bolin ·
2026-05-05 16:02:06 -07:00 -
Inject state DB, agent graph store (#20689)
## Why We want the agent graph store to be passed down the stack as a real dependency, the same way we already treat the thread store. This will let us inject the agent graph store as a real dependency and support implementations other than the local SQLite-backed one. Right now most code instantiates a state DB and an agent graph store just-in-time. Ideally, we would not depend on the state DB directly but only read through the higher-level interfaces. This change makes the dependency boundaries explicit and moves state DB initialization to process bootstrap instead of hiding it inside local store implementations. ## What changed - `ThreadManager` now requires a `StateDbHandle` and an `AgentGraphStore` at construction time instead of treating them as optional internals. - The local store constructors no longer lazily initialize SQLite. Callers now initialize the state DB once per process and use that shared handle to build: - `LocalThreadStore` - `LocalAgentGraphStore` - App bootstraps (`app-server`, `mcp-server`, `prompt_debug`, and the thread-manager sample) now initialize the state DB up front and inject the resulting handle down the stack. - `app-server` now consistently uses its process-scoped state DB handle instead of reopening SQLite or trying to recover it from loaded threads. - Device-key storage now reuses the shared state DB handle instead of maintaining its own lazy opener. - The thread archive / descendant traversal paths now use the injected `AgentGraphStore` instead of reaching through local thread-store-specific state. ## Verification - `cargo check -p codex-core -p codex-thread-store -p codex-app-server -p codex-mcp-server -p codex-thread-manager-sample --tests` - `cargo test -p codex-thread-store` - `cargo test -p codex-core thread_manager_accepts_separate_agent_graph_store_and_thread_store -- --nocapture` - `cargo test -p codex-app-server thread_archive_archives_spawned_descendants -- --nocapture`
Rasmus Rygaard ·
2026-05-05 21:45:29 +00:00 -
[codex] fix TUI turn items view fixtures (#21243)
## Summary Adds the required `items_view` field to the three session picker `Turn` test fixtures that populate full turn item lists. ## Root Cause `#21063` added `Turn.items_view` to the app-server protocol type. The later session picker merge added three test-only `codex_app_server_protocol::Turn` literals without the new field, which broke Bazel compilation on `main` with `E0063: missing field items_view`. ## Validation - `just fmt` - `cargo test -p codex-tui resume_picker --no-fail-fast` - `just argument-comment-lint` I also ran `cargo test -p codex-tui`; it compiled and ran the suite, but this local machine failed two pre-existing status permission-profile tests because `/etc/codex/requirements.toml` disallows `DangerFullAccess`.
Felipe Coury ·
2026-05-05 14:24:28 -07:00 -
feat(tui): redesign session picker (#20065)
## Why The resume/fork picker is becoming the main way users recover previous work, but the old fixed table made sessions hard to scan once thread names, branches, working directories, and timestamps all mattered. This redesign makes the picker denser by default, easier to search, and safer to inspect before resuming or forking. <table> <tr> <td> <img width="1660" height="1103" alt="CleanShot 2026-05-03 at 12 34 10" src="https://github.com/user-attachments/assets/313ede1d-1da4-4863-acd2-56b3e27e9703" /> </td> <td> <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 34 15" src="https://github.com/user-attachments/assets/cfde7d5c-bab0-4994-a807-254e53f344ea" /> </td> </tr> <tr> <td> <img width="1664" height="1107" alt="CleanShot 2026-05-03 at 12 39 22" src="https://github.com/user-attachments/assets/e1ee58ca-4dc5-4a35-ae0f-47562da3974c" /> </td> <td> <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 35 09" src="https://github.com/user-attachments/assets/9c888072-eedf-4f45-985c-0c14df28bcc7" /> </td> </tr> </table> ## What Changed - Replaces the old session table with responsive session rows that prioritize the session name or preview, then show timestamp, cwd, and branch metadata. - Makes dense view the default while keeping comfortable view available through `Ctrl+O`. - Persists the picker view preference in `[tui].session_picker_view`, including active profile-scoped config. - Adds sort/filter controls for updated time, created time, cwd, and all sessions. - Expands search matching across session name, preview, thread id, branch, and cwd. - Makes `Esc` safer in search mode: it clears an active query before starting a new session. - Adds lazy transcript inspection: - `Space` expands recent transcript context inline. - `Ctrl+T` opens a transcript overlay. - raw reasoning visibility follows `show_raw_agent_reasoning`. - Keeps remote cwd filtering server-side for remote app-server sessions so local path normalization does not incorrectly hide remote results. - Updates snapshots and config schema for the new picker states and config option. ## How to Test 1. Start Codex in a repo with several saved sessions. 2. Press `Ctrl+R` / resume picker entry point. 3. Confirm the picker opens in dense mode and shows session name or preview, timestamp, cwd, and branch metadata. 4. Press `Ctrl+O` and confirm it switches between dense and comfortable views. 5. Restart Codex and confirm the selected view persists. 6. Type a query that matches a branch, cwd, thread id, or session name; confirm matching sessions appear. 7. Press `Esc` while the query is non-empty and confirm it clears search instead of starting a new session. 8. Select a session and press `Space`; confirm recent transcript context expands inline. 9. Press `Ctrl+T`; confirm the transcript overlay opens and respects raw-reasoning visibility settings. Targeted tests: - `cargo test -p codex-tui resume_picker --no-fail-fast` - `cargo test -p codex-core runtime_config_resolves_session_picker_view_default_and_override` - `cargo test -p codex-core profile_tui_rejects_unsupported_settings` - `cargo check -p codex-thread-manager-sample` - `cargo insta pending-snapshots`
Felipe Coury ·
2026-05-05 13:32:54 -07:00 -
feat(tui): route /diff through workspace commands (#21001)
Stacked on #20892. ## Why #20892 adds the TUI workspace command abstraction so branch status metadata can run through app-server instead of assuming the CLI process has the active workspace locally. `/diff` still used direct local process execution, which means remote app-server sessions could compute the diff against the wrong machine or fail to see the active workspace at all. This PR moves `/diff` onto that same app-server-backed command path so Git runs wherever the active workspace lives. ## What Changed - Route `/diff` through the TUI `WorkspaceCommandExecutor` using the active chat cwd. - Replace direct `tokio::process::Command` usage in `get_git_diff` with argv-based workspace command requests. - Preserve the existing `/diff` behavior: tracked diff output, untracked file diffs, treating Git diff exit code `1` as success, and showing the existing non-git-repository message. - Extend `WorkspaceCommand` with caller-set timeouts and an explicit uncapped-output opt-out. Metadata probes remain capped by default; `/diff` opts out because its full output is the user-visible payload. ## How to Test Manual reviewer path: 1. Start the Codex TUI from a Git worktree with one tracked file change and one untracked file. 2. Run `/diff`. 3. Confirm the rendered diff includes both the tracked diff and the untracked file diff. 4. Start the TUI outside a Git worktree, or switch to a non-git cwd, then run `/diff`. 5. Confirm it shows the existing `/diff` not-inside-a-git-repository message. Targeted tests run: - `cargo test -p codex-tui get_git_diff -- --nocapture` - `cargo test -p codex-tui branch_summary -- --nocapture` - `cargo test -p codex-tui`
Felipe Coury ·
2026-05-05 17:09:25 -03:00 -
add turn items view to app-server turns (#21063)
## Why `Turn.items` currently overloads an empty array to mean either that no items exist or that the server intentionally did not load them for this response. That ambiguity blocks future lazy-loading work where clients need to distinguish unloaded, summary, and fully hydrated turn payloads. ## What changed - add a new `TurnItemsView` enum with `notLoaded`, `summary`, and `full` variants - add required `itemsView` metadata to app-server `Turn` payloads - mark reconstructed persisted history as `full` and live shell-style turn payloads as `notLoaded` - keep current `thread/turns/list` behavior unchanged and document that it still returns `full` turns today - regenerate the JSON and TypeScript protocol fixtures ## Verification - `just write-app-server-schema` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-app-server thread_read_can_include_turns` - `cargo test -p codex-app-server thread_turns_list_can_page_backward_and_forward` - `cargo test -p codex-app-server thread_resume_rejects_history_when_thread_is_running` - `just fix -p codex-app-server-protocol` - `just fix -p codex-app-server` - `just fmt`
rhan-oai ·
2026-05-05 19:17:16 +00:00 -
hook trust metadata and enforcement (#20321)
# Why We want shared hook trust that both the app and the TUI can build on, but the metadata is only useful if runtime behavior agrees with it. This PR adds a single backend trust model for hooks so unmanaged hooks cannot run until the current definition has been reviewed, while managed hooks remain runnable and non-configurable. # What - persist `trusted_hash` alongside hook state in `config.toml` - expose `currentHash` and derived `trustStatus` through `hooks/list` - derive trust from normalized hook definitions so equivalent hooks from `config.toml` and `hooks.json` share the same trust identity - gate unmanaged hooks on trust before they enter the runnable handler set # Reviewer Notes - key file to review is `codex-rs/hooks/src/engine/discovery.rs` - the only **core** change is schema related
Abhinav ·
2026-05-05 19:13:55 +00:00 -
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`
Felipe Coury ·
2026-05-05 11:17:47 -07:00 -
fix(tui): external editor expansion for same-size large pastes (#21190)
## Why We found this while reviewing #21091, but confirmed it is not introduced by that PR: the order-sensitive `current_text_with_pending()` replacement loop already existed, and `main` already allowed active same-size large pastes to use prefix-overlapping labels such as `[Pasted Content N chars]` and `[Pasted Content N chars] #2`. #21091 fixes placeholder numbering after a draft is cleared, so a fresh same-size paste can reuse the base label. This PR fixes a different path: when a draft already contains multiple active same-size large pastes, the placeholders can overlap by prefix, for example `[Pasted Content N chars]` and `[Pasted Content N chars] #2`. That overlap breaks `current_text_with_pending()` when the composer materializes the draft text for the external editor. Replacing the base placeholder first can partially rewrite the `#2` placeholder, leaving the external editor seeded with corrupted text instead of both paste payloads. | Before | After | |---|---| | <img width="1230" height="1008" alt="CleanShot 2026-05-05 at 10 18 09" src="https://github.com/user-attachments/assets/88a2936c-cf00-4adc-8567-8fd8f398b4a8" /> | <img width="1230" height="1008" alt="CleanShot 2026-05-05 at 10 20 31" src="https://github.com/user-attachments/assets/119cff52-43c8-432a-9367-418d82f4ed82" /> | | <img width="1230" height="1008" alt="CleanShot 2026-05-05 at 10 18 57" src="https://github.com/user-attachments/assets/026031bb-839b-4252-a0fd-9ba9616435fe" /> | <img width="1230" height="1008" alt="CleanShot 2026-05-05 at 10 21 31" src="https://github.com/user-attachments/assets/8cb6f2c8-3a5d-411b-8623-dca666ee3c08" /> | ## What Changed - Changed `current_text_with_pending()` to expand pending pastes through the existing element-range based `expand_pending_pastes()` helper instead of global string replacement. - Added a regression test with two different same-length large pastes to ensure both overlapping placeholders expand to their original payloads. ## How to Test 1. Start Codex TUI. 2. Paste a large string, for example 1004 `A` characters. ```shell perl -e 'print "A" x 1004' | pbcopy ``` 3. Paste a second large string with the same length, for example 1004 `B` characters. ```shell perl -e 'print "B" x 1004' | pbcopy ``` 4. Open the external editor from the composer. 5. Confirm the editor is seeded with the full `A...` payload followed by the full `B...` payload, with no literal `#2` left behind. Targeted tests: - `cargo test -p codex-tui current_text_with_pending_expands_overlapping_placeholders` - `just argument-comment-lint-from-source -p codex-tui` I also ran `cargo test -p codex-tui`; it reached the full crate suite but failed two unrelated local status tests because this machine's `/etc/codex/requirements.toml` rejects `DangerFullAccess`.
Felipe Coury ·
2026-05-05 14:41:43 -03:00 -
[codex] Fix fork --last cwd filtering (#21089)
Fixes #20945. This keeps `codex fork --last` aligned with the neighboring latest-session lookup flows. The local fork path now uses the same cwd-scope helper as `resume --last`, which is also a small code cleanup around how this selection logic is shared. Credit to @chanwooyang1 for the report and for pointing out the narrow fix direction. What changed: - Route `fork --last` through the shared latest-session cwd filter. - Preserve `--all` as the explicit opt-in for global latest-session selection. - Keep remote cwd override behavior unchanged. - Add focused coverage for local default, `--all`, and remote override filter semantics. Validation: - Ran `just fmt`. - Ran `git diff --check`. - Reviewed the `fork --last`, `resume --last`, and fork picker selection paths against the issue report.
canvrno-oai ·
2026-05-05 10:33:40 -07:00 -
[codex] Fix TUI large paste placeholder numbering after Ctrl+C (#21091)
Fixes #19940. Large-paste placeholder numbering was backed by a per-size counter, so clearing a draft with `Ctrl+C` left numbering state behind even though the active pending paste state was gone. This updates the composer to derive the next placeholder suffix from active pending pastes instead, which keeps simultaneous same-size pastes distinct while letting fresh drafts reuse the base label. This is also a small code cleanup: pending paste state is now the source of truth instead of maintaining a separate counter. Credit to @Sungyoun-Kim for the issue report, root-cause notes, and fork with the proposed fix, and to @charley-oai for the earlier related #10032 proposal. Changes: - Remove the monotonic large-paste counter from the composer. - Compute suffixes from currently active pending paste placeholders. - Document large-paste placeholder behavior in the composer module docs. - Add regression coverage for `Ctrl+C` clearing and deletion/reset behavior. Testing: - `just fmt` - `git diff --check`
canvrno-oai ·
2026-05-05 10:33:37 -07:00 -
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.
Eric Traut ·
2026-05-05 09:55:07 -07:00 -
fix(tui): make /copy work inside tmux without passthrough (#20207)
## Summary - prefer tmux's native clipboard integration for `/copy` when running inside tmux - fall back to OSC 52 when tmux clipboard copy is unavailable - add coverage for tmux-preferred, fallback, and combined-failure paths ## Why Inside tmux, `/copy` previously relied on DCS-wrapped OSC 52 when `TMUX` was set. That only reaches the outer terminal when tmux passthrough is enabled, so Codex could report success even though the system clipboard never changed. ## User impact `/copy` now works inside tmux even when `allow-passthrough` is off, as long as tmux clipboard integration is available. If tmux cannot handle the copy, Codex still keeps the existing OSC 52 fallback path. ## Validation - `cargo test -p codex-tui` - `just fmt` - `just fix -p codex-tui` - `just argument-comment-lint` - manually verified `/copy` inside tmux with `allow-passthrough off` Fixes #19926
Felipe Coury ·
2026-05-05 16:18:02 +00:00 -
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>
Ahmed Ibrahim ·
2026-05-05 09:51:18 +03:00 -
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
alexsong-oai ·
2026-05-04 20:36:29 -07:00 -
[codex] Add unsandboxed process exec API (#19040)
## Why App-server clients sometimes need argv-based local process execution while sandbox policy is controlled outside Codex. Those environments can reject sandbox-disabling paths before a command ever starts, even when the caller intentionally wants unsandboxed execution. This PR adds a distinct `process/*` API for that use case instead of extending `command/exec` with another sandbox-disabling shape. Keeping the new surface separate also makes the future removal of `command/exec` simpler: clients that need explicit process lifecycle control can move to the newer handle-based API without depending on `command/exec` business logic. ## What changed - Added v2 process lifecycle methods: `process/spawn`, `process/writeStdin`, `process/resizePty`, and `process/kill`. - Added process notifications: `process/outputDelta` for streamed stdout/stderr chunks and `process/exited` for final exit status and buffered output. - Made `process/spawn` intentionally unsandboxed and omitted sandbox-selection fields such as `sandboxPolicy` and `permissionProfile`. - Added client-supplied, connection-scoped `processHandle` values for follow-up control requests and notification routing. - Supported cwd, environment overrides, PTY mode and size, stdin streaming, stdout/stderr streaming, per-stream output caps, and timeout controls. - Killed active process sessions when the originating app-server connection closes. - Wired the implementation through the modular `request_processors/` app-server layout, with process-handle request serialization for follow-up control calls. - Updated generated JSON/TypeScript schema fixtures and documented the new API in `codex-rs/app-server/README.md`. - Added v2 app-server integration coverage in `codex-rs/app-server/tests/suite/v2/process_exec.rs` for spawn acknowledgement before exit, buffered output caps, and process termination. ## Verification - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-app-server` --------- Co-authored-by: Owen Lin <owen@openai.com>
Ruslan Nigmatullin ·
2026-05-04 16:43:58 -07:00