mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
Show action required in terminal title (#18372)
Implements #18162 This updates the TUI terminal title to show an explicit action-required state when Codex is blocked on user approval or input. The terminal title now uses the activity title item to cover both active work and blocked-on-user states, while still accepting the legacy spinner config value. Changes - Rename the terminal title item from `spinner` to `activity` while preserving legacy config compatibility - Show `[ ! ] Action Required `while approval or input overlays are active, with a blinking `[ . ]` alternate state - Suppress the normal working spinner while Codex is blocked on user action - Add targeted coverage for action-required title behavior and legacy title-item parsing Testing - Trigger an approval or input modal and confirm the tab title alternates between `[ ! ] Action Required` and `[ . ] Action Required` - Disable the activity title item and confirm the action-required title does not appear - Resolve the prompt and confirm the title returns to the normal spinning/idel state https://github.com/user-attachments/assets/e9ecc530-a6be-4fd7-b9a6-d550a790eb2c
This commit is contained in:
1 parent
e903d000b0
commit
e64c765673
20 files changed
+450
-68
No files matched your search
@@ -301,6 +301,7 @@ pub(super) async fn make_chatwidget_manual(
|
||||
status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),
|
||||
terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),
|
||||
last_terminal_title: None,
|
||||
last_terminal_title_requires_action: false,
|
||||
terminal_title_setup_original_items: None,
|
||||
terminal_title_animation_origin: Instant::now(),
|
||||
status_line_project_root_name_cache: None,
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
//! Terminal-title focused tests for live chatwidget status-surface behavior.
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn terminal_title_shows_action_required_while_exec_approval_is_pending() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
chat.refresh_terminal_title();
|
||||
|
||||
let request = ExecApprovalRequestEvent {
|
||||
call_id: "call-action-required".into(),
|
||||
approval_id: Some("call-action-required".into()),
|
||||
turn_id: "turn-action-required".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo hello".into()],
|
||||
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
reason: Some("need confirmation".into()),
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
proposed_network_policy_amendments: None,
|
||||
additional_permissions: None,
|
||||
available_decisions: None,
|
||||
parsed_cmd: vec![],
|
||||
};
|
||||
chat.handle_codex_event(Event {
|
||||
id: "sub-action-required".into(),
|
||||
msg: EventMsg::ExecApprovalRequest(request),
|
||||
});
|
||||
|
||||
chat.pre_draw_tick();
|
||||
|
||||
assert_eq!(
|
||||
chat.last_terminal_title,
|
||||
Some("[ ! ] Action Required | project".to_string())
|
||||
);
|
||||
assert!(!chat.should_animate_terminal_title_spinner());
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
|
||||
chat.pre_draw_tick();
|
||||
|
||||
let title = chat
|
||||
.last_terminal_title
|
||||
.as_deref()
|
||||
.expect("terminal title should be restored after approval");
|
||||
assert!(title.contains("project"));
|
||||
assert!(!title.contains("Action Required"));
|
||||
assert!(chat.should_animate_terminal_title_spinner());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn terminal_title_action_required_respects_spinner_setting() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.tui_terminal_title = Some(vec!["project".to_string()]);
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
chat.refresh_terminal_title();
|
||||
|
||||
let request = ExecApprovalRequestEvent {
|
||||
call_id: "call-no-spinner".into(),
|
||||
approval_id: Some("call-no-spinner".into()),
|
||||
turn_id: "turn-no-spinner".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo hello".into()],
|
||||
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
reason: Some("need confirmation".into()),
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
proposed_network_policy_amendments: None,
|
||||
additional_permissions: None,
|
||||
available_decisions: None,
|
||||
parsed_cmd: vec![],
|
||||
};
|
||||
chat.handle_codex_event(Event {
|
||||
id: "sub-no-spinner".into(),
|
||||
msg: EventMsg::ExecApprovalRequest(request),
|
||||
});
|
||||
|
||||
chat.pre_draw_tick();
|
||||
|
||||
assert_eq!(chat.last_terminal_title, Some("project".to_string()));
|
||||
assert!(!chat.should_animate_terminal_title_action_required());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn terminal_title_action_required_blinks_when_animations_are_enabled() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
chat.terminal_title_animation_origin = Instant::now() - std::time::Duration::from_millis(1500);
|
||||
chat.refresh_terminal_title();
|
||||
|
||||
let request = ExecApprovalRequestEvent {
|
||||
call_id: "call-blink".into(),
|
||||
approval_id: Some("call-blink".into()),
|
||||
turn_id: "turn-blink".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo hello".into()],
|
||||
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
reason: Some("need confirmation".into()),
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
proposed_network_policy_amendments: None,
|
||||
additional_permissions: None,
|
||||
available_decisions: None,
|
||||
parsed_cmd: vec![],
|
||||
};
|
||||
chat.handle_codex_event(Event {
|
||||
id: "sub-blink".into(),
|
||||
msg: EventMsg::ExecApprovalRequest(request),
|
||||
});
|
||||
|
||||
chat.pre_draw_tick();
|
||||
|
||||
assert_eq!(
|
||||
chat.last_terminal_title,
|
||||
Some("[ . ] Action Required | project".to_string())
|
||||
);
|
||||
assert!(chat.should_animate_terminal_title_action_required());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn terminal_title_activity_indicators_do_not_animate_when_animations_are_disabled() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.animations = false;
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
chat.terminal_title_animation_origin = Instant::now() - std::time::Duration::from_millis(1500);
|
||||
chat.refresh_terminal_title();
|
||||
|
||||
assert_eq!(chat.last_terminal_title, Some("project".to_string()));
|
||||
assert!(!chat.should_animate_terminal_title_spinner());
|
||||
|
||||
let request = ExecApprovalRequestEvent {
|
||||
call_id: "call-no-animations".into(),
|
||||
approval_id: Some("call-no-animations".into()),
|
||||
turn_id: "turn-no-animations".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo hello".into()],
|
||||
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
reason: Some("need confirmation".into()),
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
proposed_network_policy_amendments: None,
|
||||
additional_permissions: None,
|
||||
available_decisions: None,
|
||||
parsed_cmd: vec![],
|
||||
};
|
||||
chat.handle_codex_event(Event {
|
||||
id: "sub-no-animations".into(),
|
||||
msg: EventMsg::ExecApprovalRequest(request),
|
||||
});
|
||||
|
||||
chat.pre_draw_tick();
|
||||
|
||||
assert_eq!(
|
||||
chat.last_terminal_title,
|
||||
Some("[ ! ] Action Required | project".to_string())
|
||||
);
|
||||
assert!(!chat.should_animate_terminal_title_action_required());
|
||||
}
|
||||
Reference in new issue
Block a user