Revert "feat: unified exec footer" (#8109)

Reverts openai/codex#8067
This commit is contained in:
jif-oai 2025-12-16 18:03:19 +01:00 committed by GitHub
parent d7482510b1
commit b53889aed5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 10 additions and 393 deletions

View file

@ -3,7 +3,6 @@ use std::path::PathBuf;
use crate::app_event_sender::AppEventSender;
use crate::bottom_pane::queued_user_messages::QueuedUserMessages;
use crate::bottom_pane::unified_exec_footer::UnifiedExecFooter;
use crate::render::renderable::FlexRenderable;
use crate::render::renderable::Renderable;
use crate::render::renderable::RenderableItem;
@ -41,7 +40,6 @@ mod queued_user_messages;
mod scroll_state;
mod selection_popup_common;
mod textarea;
mod unified_exec_footer;
pub(crate) use feedback_view::FeedbackNoteView;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -78,8 +76,6 @@ pub(crate) struct BottomPane {
/// Inline status indicator shown above the composer while a task is running.
status: Option<StatusIndicatorWidget>,
/// Unified exec session summary shown above the composer.
unified_exec_footer: UnifiedExecFooter,
/// Queued user messages to show above the composer while a turn is running.
queued_user_messages: QueuedUserMessages,
context_window_percent: Option<i64>,
@ -127,7 +123,6 @@ impl BottomPane {
is_task_running: false,
ctrl_c_quit_hint: false,
status: None,
unified_exec_footer: UnifiedExecFooter::new(),
queued_user_messages: QueuedUserMessages::new(),
esc_backtrack_hint: false,
animations_enabled,
@ -398,12 +393,6 @@ impl BottomPane {
self.request_redraw();
}
pub(crate) fn set_unified_exec_sessions(&mut self, sessions: Vec<String>) {
if self.unified_exec_footer.set_sessions(sessions) {
self.request_redraw();
}
}
/// Update custom prompts available for the slash popup.
pub(crate) fn set_custom_prompts(&mut self, prompts: Vec<CustomPrompt>) {
self.composer.set_custom_prompts(prompts);
@ -534,14 +523,8 @@ impl BottomPane {
if let Some(status) = &self.status {
flex.push(0, RenderableItem::Borrowed(status));
}
if !self.unified_exec_footer.is_empty() {
flex.push(0, RenderableItem::Borrowed(&self.unified_exec_footer));
}
flex.push(1, RenderableItem::Borrowed(&self.queued_user_messages));
if self.status.is_some()
|| !self.unified_exec_footer.is_empty()
|| !self.queued_user_messages.messages.is_empty()
{
if self.status.is_some() || !self.queued_user_messages.messages.is_empty() {
flex.push(0, RenderableItem::Owned("".into()));
}
let mut flex2 = FlexRenderable::new();

View file

@ -1,22 +0,0 @@
---
source: tui/src/bottom_pane/unified_exec_footer.rs
expression: "format!(\"{buf:?}\")"
---
Buffer {
area: Rect { x: 0, y: 0, width: 50, height: 2 },
content: [
"Background terminal running: echo hello · rg "foo"",
" src · 1 more running ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 28, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 29, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 39, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 42, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 29, y: 1, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 32, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 49, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}

View file

@ -1,21 +0,0 @@
---
source: tui/src/bottom_pane/unified_exec_footer.rs
expression: "format!(\"{buf:?}\")"
---
Buffer {
area: Rect { x: 0, y: 0, width: 50, height: 2 },
content: [
"Background terminal running: echo hello · rg "foo"",
" src ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 28, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 29, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 39, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 42, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 29, y: 1, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 32, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}

View file

@ -1,125 +0,0 @@
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use crate::render::renderable::Renderable;
use crate::text_formatting::truncate_text;
use crate::wrapping::RtOptions;
use crate::wrapping::word_wrap_lines;
const MAX_SESSION_LABEL_GRAPHEMES: usize = 48;
const MAX_VISIBLE_SESSIONS: usize = 2;
pub(crate) struct UnifiedExecFooter {
sessions: Vec<String>,
}
impl UnifiedExecFooter {
pub(crate) fn new() -> Self {
Self {
sessions: Vec::new(),
}
}
pub(crate) fn set_sessions(&mut self, sessions: Vec<String>) -> bool {
if self.sessions == sessions {
return false;
}
self.sessions = sessions;
true
}
pub(crate) fn is_empty(&self) -> bool {
self.sessions.is_empty()
}
fn render_lines(&self, width: u16) -> Vec<Line<'static>> {
if self.sessions.is_empty() || width < 4 {
return Vec::new();
}
let label = "Background terminal running:";
let mut spans = Vec::new();
spans.push(label.dim());
spans.push(" ".into());
let visible = self.sessions.iter().take(MAX_VISIBLE_SESSIONS);
let mut visible_count = 0usize;
for (idx, command) in visible.enumerate() {
if idx > 0 {
spans.push(" · ".dim());
}
let truncated = truncate_text(command, MAX_SESSION_LABEL_GRAPHEMES);
spans.push(truncated.cyan());
visible_count += 1;
}
let remaining = self.sessions.len().saturating_sub(visible_count);
if remaining > 0 {
spans.push(" · ".dim());
spans.push(format!("{remaining} more running").dim());
}
let indent = " ".repeat(label.len() + 1);
let line = Line::from(spans);
word_wrap_lines(
std::iter::once(line),
RtOptions::new(width as usize).subsequent_indent(Line::from(indent).dim()),
)
}
}
impl Renderable for UnifiedExecFooter {
fn render(&self, area: Rect, buf: &mut Buffer) {
if area.is_empty() {
return;
}
Paragraph::new(self.render_lines(area.width)).render(area, buf);
}
fn desired_height(&self, width: u16) -> u16 {
self.render_lines(width).len() as u16
}
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
#[test]
fn desired_height_empty() {
let footer = UnifiedExecFooter::new();
assert_eq!(footer.desired_height(40), 0);
}
#[test]
fn render_two_sessions() {
let mut footer = UnifiedExecFooter::new();
footer.set_sessions(vec!["echo hello".to_string(), "rg \"foo\" src".to_string()]);
let width = 50;
let height = footer.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
footer.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_two_sessions", format!("{buf:?}"));
}
#[test]
fn render_more_sessions() {
let mut footer = UnifiedExecFooter::new();
footer.set_sessions(vec![
"echo hello".to_string(),
"rg \"foo\" src".to_string(),
"cat README.md".to_string(),
]);
let width = 50;
let height = footer.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
footer.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_more_sessions", format!("{buf:?}"));
}
}

View file

@ -99,7 +99,6 @@ use crate::diff_render::display_path_for;
use crate::exec_cell::CommandOutput;
use crate::exec_cell::ExecCell;
use crate::exec_cell::new_active_exec_command;
use crate::exec_command::strip_bash_lc_and_escape;
use crate::get_git_diff::get_git_diff;
use crate::history_cell;
use crate::history_cell::AgentMessageCell;
@ -150,11 +149,6 @@ struct RunningCommand {
source: ExecCommandSource,
}
struct UnifiedExecSessionSummary {
key: String,
command_display: String,
}
struct UnifiedExecWaitState {
command_display: String,
}
@ -169,13 +163,6 @@ impl UnifiedExecWaitState {
}
}
fn is_unified_exec_source(source: ExecCommandSource) -> bool {
matches!(
source,
ExecCommandSource::UnifiedExecStartup | ExecCommandSource::UnifiedExecInteraction
)
}
const RATE_LIMIT_WARNING_THRESHOLDS: [f64; 3] = [75.0, 90.0, 95.0];
const NUDGE_MODEL_SLUG: &str = "gpt-5.1-codex-mini";
const RATE_LIMIT_SWITCH_PROMPT_THRESHOLD: f64 = 90.0;
@ -313,7 +300,6 @@ pub(crate) struct ChatWidget {
suppressed_exec_calls: HashSet<String>,
last_unified_wait: Option<UnifiedExecWaitState>,
task_complete_pending: bool,
unified_exec_sessions: Vec<UnifiedExecSessionSummary>,
mcp_startup_status: Option<HashMap<String, McpStartupStatus>>,
// Queue of interruptive UI events deferred during an active write cycle
interrupts: InterruptManager,
@ -842,10 +828,6 @@ impl ChatWidget {
fn on_exec_command_begin(&mut self, ev: ExecCommandBeginEvent) {
self.flush_answer_stream_with_separator();
if is_unified_exec_source(ev.source) {
self.track_unified_exec_session_begin(&ev);
return;
}
let ev2 = ev.clone();
self.defer_or_handle(|q| q.push_exec_begin(ev), |s| s.handle_exec_begin_now(ev2));
}
@ -857,18 +839,8 @@ impl ChatWidget {
// TODO: Handle streaming exec output if/when implemented
}
fn on_terminal_interaction(&mut self, ev: TerminalInteractionEvent) {
self.flush_answer_stream_with_separator();
let key = Self::unified_exec_session_key(Some(&ev.process_id), &ev.call_id);
let command_display = self
.unified_exec_sessions
.iter()
.find(|session| session.key == key)
.map(|session| session.command_display.clone());
self.add_to_history(history_cell::new_unified_exec_interaction(
command_display,
ev.stdin,
));
fn on_terminal_interaction(&mut self, _ev: TerminalInteractionEvent) {
// TODO: Handle once design is ready
}
fn on_patch_apply_begin(&mut self, event: PatchApplyBeginEvent) {
@ -896,58 +868,10 @@ impl ChatWidget {
}
fn on_exec_command_end(&mut self, ev: ExecCommandEndEvent) {
if is_unified_exec_source(ev.source) {
self.track_unified_exec_session_end(&ev);
return;
}
let ev2 = ev.clone();
self.defer_or_handle(|q| q.push_exec_end(ev), |s| s.handle_exec_end_now(ev2));
}
fn unified_exec_session_key(process_id: Option<&str>, call_id: &str) -> String {
process_id.unwrap_or(call_id).to_string()
}
fn track_unified_exec_session_begin(&mut self, ev: &ExecCommandBeginEvent) {
if ev.source != ExecCommandSource::UnifiedExecStartup {
return;
}
let key = Self::unified_exec_session_key(ev.process_id.as_deref(), &ev.call_id);
let command_display = strip_bash_lc_and_escape(&ev.command);
if let Some(existing) = self
.unified_exec_sessions
.iter_mut()
.find(|session| session.key == key)
{
existing.command_display = command_display;
} else {
self.unified_exec_sessions.push(UnifiedExecSessionSummary {
key,
command_display,
});
}
self.sync_unified_exec_footer();
}
fn track_unified_exec_session_end(&mut self, ev: &ExecCommandEndEvent) {
let key = Self::unified_exec_session_key(ev.process_id.as_deref(), &ev.call_id);
let before = self.unified_exec_sessions.len();
self.unified_exec_sessions
.retain(|session| session.key != key);
if self.unified_exec_sessions.len() != before {
self.sync_unified_exec_footer();
}
}
fn sync_unified_exec_footer(&mut self) {
let sessions = self
.unified_exec_sessions
.iter()
.map(|session| session.command_display.clone())
.collect();
self.bottom_pane.set_unified_exec_sessions(sessions);
}
fn on_mcp_tool_call_begin(&mut self, ev: McpToolCallBeginEvent) {
let ev2 = ev.clone();
self.defer_or_handle(|q| q.push_mcp_begin(ev), |s| s.handle_mcp_begin_now(ev2));
@ -1395,7 +1319,6 @@ impl ChatWidget {
suppressed_exec_calls: HashSet::new(),
last_unified_wait: None,
task_complete_pending: false,
unified_exec_sessions: Vec::new(),
mcp_startup_status: None,
interrupts: InterruptManager::new(),
reasoning_buffer: String::new(),
@ -1481,7 +1404,6 @@ impl ChatWidget {
suppressed_exec_calls: HashSet::new(),
last_unified_wait: None,
task_complete_pending: false,
unified_exec_sessions: Vec::new(),
mcp_startup_status: None,
interrupts: InterruptManager::new(),
reasoning_buffer: String::new(),
@ -1949,20 +1871,12 @@ impl ChatWidget {
EventMsg::ElicitationRequest(ev) => {
self.on_elicitation_request(ev);
}
EventMsg::ExecCommandBegin(ev) => {
if !from_replay || !is_unified_exec_source(ev.source) {
self.on_exec_command_begin(ev);
}
}
EventMsg::ExecCommandBegin(ev) => self.on_exec_command_begin(ev),
EventMsg::TerminalInteraction(delta) => self.on_terminal_interaction(delta),
EventMsg::ExecCommandOutputDelta(delta) => self.on_exec_command_output_delta(delta),
EventMsg::PatchApplyBegin(ev) => self.on_patch_apply_begin(ev),
EventMsg::PatchApplyEnd(ev) => self.on_patch_apply_end(ev),
EventMsg::ExecCommandEnd(ev) => {
if !from_replay || !is_unified_exec_source(ev.source) {
self.on_exec_command_end(ev);
}
}
EventMsg::ExecCommandEnd(ev) => self.on_exec_command_end(ev),
EventMsg::ViewImageToolCall(ev) => self.on_view_image_tool_call(ev),
EventMsg::McpToolCallBegin(ev) => self.on_mcp_tool_call_begin(ev),
EventMsg::McpToolCallEnd(ev) => self.on_mcp_tool_call_end(ev),

View file

@ -426,7 +426,6 @@ fn make_chatwidget_manual(
suppressed_exec_calls: HashSet::new(),
last_unified_wait: None,
task_complete_pending: false,
unified_exec_sessions: Vec::new(),
mcp_startup_status: None,
interrupts: InterruptManager::new(),
reasoning_buffer: String::new(),
@ -1220,7 +1219,7 @@ fn exec_end_without_begin_uses_event_command() {
}
#[test]
fn exec_history_skips_unified_exec_startup_commands() {
fn exec_history_shows_unified_exec_startup_commands() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);
let begin = begin_exec_with_source(
@ -1237,31 +1236,11 @@ fn exec_history_skips_unified_exec_startup_commands() {
end_exec(&mut chat, begin, "echo unified exec startup\n", "", 0);
let cells = drain_insert_history(&mut rx);
assert_eq!(cells.len(), 1, "expected finalized exec cell to flush");
let blob = lines_to_single_string(&cells[0]);
assert!(
cells.is_empty(),
"expected unified exec startup to render in footer only"
);
}
#[test]
fn unified_exec_end_after_task_complete_is_suppressed() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);
let begin = begin_exec_with_source(
&mut chat,
"call-startup",
"echo unified exec startup",
ExecCommandSource::UnifiedExecStartup,
);
drain_insert_history(&mut rx);
chat.on_task_complete(None);
end_exec(&mut chat, begin, "", "", 0);
let cells = drain_insert_history(&mut rx);
assert!(
cells.is_empty(),
"expected unified exec end after task complete to be suppressed"
blob.contains("• Ran echo unified exec startup"),
"expected startup command to render: {blob:?}"
);
}

View file

@ -375,72 +375,6 @@ impl HistoryCell for PrefixedWrappedHistoryCell {
}
}
#[derive(Debug)]
pub(crate) struct UnifiedExecInteractionCell {
command_display: Option<String>,
stdin: String,
}
impl UnifiedExecInteractionCell {
pub(crate) fn new(command_display: Option<String>, stdin: String) -> Self {
Self {
command_display,
stdin,
}
}
}
impl HistoryCell for UnifiedExecInteractionCell {
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
if width == 0 {
return Vec::new();
}
let wrap_width = width as usize;
let mut header_spans = vec!["".dim(), "Interacted with background terminal".bold()];
if let Some(command) = &self.command_display
&& !command.is_empty()
{
header_spans.push(" · ".dim());
header_spans.push(command.clone().dim());
}
let header = Line::from(header_spans);
let mut out: Vec<Line<'static>> = Vec::new();
let header_wrapped = word_wrap_line(&header, RtOptions::new(wrap_width));
push_owned_lines(&header_wrapped, &mut out);
let input_lines: Vec<Line<'static>> = if self.stdin.is_empty() {
vec![vec!["(waited)".dim()].into()]
} else {
self.stdin
.lines()
.map(|line| Line::from(line.to_string()))
.collect()
};
let input_wrapped = word_wrap_lines(
input_lines,
RtOptions::new(wrap_width)
.initial_indent(Line::from("".dim()))
.subsequent_indent(Line::from(" ".dim())),
);
out.extend(input_wrapped);
out
}
fn desired_height(&self, width: u16) -> u16 {
self.display_lines(width).len() as u16
}
}
pub(crate) fn new_unified_exec_interaction(
command_display: Option<String>,
stdin: String,
) -> UnifiedExecInteractionCell {
UnifiedExecInteractionCell::new(command_display, stdin)
}
fn truncate_exec_snippet(full_cmd: &str) -> String {
let mut snippet = match full_cmd.split_once('\n') {
Some((first, _)) => format!("{first} ..."),
@ -1624,31 +1558,6 @@ mod tests {
render_lines(&cell.transcript_lines(u16::MAX))
}
#[test]
fn unified_exec_interaction_cell_renders_input() {
let cell =
new_unified_exec_interaction(Some("echo hello".to_string()), "ls\npwd".to_string());
let lines = render_transcript(&cell);
assert_eq!(
lines,
vec![
"↳ Interacted with background terminal · echo hello",
" └ ls",
" pwd",
],
);
}
#[test]
fn unified_exec_interaction_cell_renders_wait() {
let cell = new_unified_exec_interaction(None, String::new());
let lines = render_transcript(&cell);
assert_eq!(
lines,
vec!["↳ Interacted with background terminal", " └ (waited)"],
);
}
#[test]
fn mcp_tools_output_masks_sensitive_values() {
let mut config = test_config();