mirror of
https://github.com/openai/codex.git
synced 2026-11-02 16:20:35 +00:00
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
This commit is contained in:
1 parent
c2fed01550
commit
cc16995cc6
22 files changed
+1351
-10
No files matched your search
@@ -76,6 +76,8 @@ use crate::tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use crate::update_action::UpdateAction;
|
||||
use crate::version::CODEX_CLI_VERSION;
|
||||
use crate::workspace_command::AppServerWorkspaceCommandRunner;
|
||||
use crate::workspace_command::WorkspaceCommandRunner;
|
||||
use codex_ansi_escape::ansi_escape_line;
|
||||
use codex_app_server_client::AppServerRequestHandle;
|
||||
use codex_app_server_client::TypedRequestError;
|
||||
@@ -432,6 +434,7 @@ pub(crate) struct App {
|
||||
pub(crate) session_telemetry: SessionTelemetry,
|
||||
pub(crate) app_event_tx: AppEventSender,
|
||||
pub(crate) chat_widget: ChatWidget,
|
||||
workspace_command_runner: Option<WorkspaceCommandRunner>,
|
||||
/// Config is stored here so we can recreate ChatWidgets as needed.
|
||||
pub(crate) config: Config,
|
||||
pub(crate) state_db: Option<StateDbHandle>,
|
||||
@@ -574,6 +577,7 @@ impl App {
|
||||
config: cfg,
|
||||
frame_requester: tui.frame_requester(),
|
||||
app_event_tx: self.app_event_tx.clone(),
|
||||
workspace_command_runner: self.workspace_command_runner.clone(),
|
||||
initial_user_message,
|
||||
enhanced_keys_supported: self.enhanced_keys_supported,
|
||||
has_chatgpt_account: self.chat_widget.has_chatgpt_account(),
|
||||
@@ -712,6 +716,9 @@ impl App {
|
||||
|
||||
let status_line_invalid_items_warned = Arc::new(AtomicBool::new(false));
|
||||
let terminal_title_invalid_items_warned = Arc::new(AtomicBool::new(false));
|
||||
let workspace_command_runner: WorkspaceCommandRunner = Arc::new(
|
||||
AppServerWorkspaceCommandRunner::new(app_server.request_handle()),
|
||||
);
|
||||
let runtime_model_provider_base_url =
|
||||
resolve_runtime_model_provider_base_url(&config.model_provider).await;
|
||||
|
||||
@@ -734,6 +741,7 @@ impl App {
|
||||
config: config.clone(),
|
||||
frame_requester: tui.frame_requester(),
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
workspace_command_runner: Some(workspace_command_runner.clone()),
|
||||
initial_user_message: crate::chatwidget::create_initial_user_message(
|
||||
initial_prompt.clone(),
|
||||
initial_images.clone(),
|
||||
@@ -769,6 +777,7 @@ impl App {
|
||||
config: config.clone(),
|
||||
frame_requester: tui.frame_requester(),
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
workspace_command_runner: Some(workspace_command_runner.clone()),
|
||||
initial_user_message: crate::chatwidget::create_initial_user_message(
|
||||
initial_prompt.clone(),
|
||||
initial_images.clone(),
|
||||
@@ -809,6 +818,7 @@ impl App {
|
||||
config: config.clone(),
|
||||
frame_requester: tui.frame_requester(),
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
workspace_command_runner: Some(workspace_command_runner.clone()),
|
||||
initial_user_message: crate::chatwidget::create_initial_user_message(
|
||||
initial_prompt.clone(),
|
||||
initial_images.clone(),
|
||||
@@ -856,6 +866,7 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
session_telemetry: session_telemetry.clone(),
|
||||
app_event_tx,
|
||||
chat_widget,
|
||||
workspace_command_runner: Some(workspace_command_runner),
|
||||
config,
|
||||
state_db,
|
||||
active_profile,
|
||||
|
||||
Reference in new issue
Block a user