mirror of
https://github.com/openai/codex.git
synced 2026-11-05 08:28:29 +00:00
TUI: skip goal replace prompt for completed goals (#23792)
## Why Users reported that the replacement confirmation feels unnecessary when the current thread goal is already complete. In that state, `/goal <objective>` is starting fresh rather than interrupting active work. ## What changed `/goal <objective>` now skips the replace confirmation when the existing goal has `complete` status and uses the existing fresh replacement path. Goals that are active, paused, blocked, usage-limited, or budget-limited still require confirmation before being replaced.
This commit is contained in:
1 parent
de80fa6e31
commit
4acb456bfe
1 file changed
+59
-7
@@ -8,6 +8,7 @@ use crate::bottom_pane::SelectionViewParams;
|
|||||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
|
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
|
||||||
use crate::goal_display::goal_status_label;
|
use crate::goal_display::goal_status_label;
|
||||||
use crate::goal_display::goal_usage_summary;
|
use crate::goal_display::goal_usage_summary;
|
||||||
|
use codex_app_server_protocol::ThreadGoal;
|
||||||
use codex_app_server_protocol::ThreadGoalStatus;
|
use codex_app_server_protocol::ThreadGoalStatus;
|
||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
|
|
||||||
@@ -116,25 +117,30 @@ impl App {
|
|||||||
objective: String,
|
objective: String,
|
||||||
mode: ThreadGoalSetMode,
|
mode: ThreadGoalSetMode,
|
||||||
) {
|
) {
|
||||||
if matches!(mode, ThreadGoalSetMode::ConfirmIfExists) {
|
let mode = if matches!(mode, ThreadGoalSetMode::ConfirmIfExists) {
|
||||||
let result = app_server.thread_goal_get(thread_id).await;
|
let result = app_server.thread_goal_get(thread_id).await;
|
||||||
if self.current_displayed_thread_id() != Some(thread_id) {
|
if self.current_displayed_thread_id() != Some(thread_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(response) if response.goal.is_some() => {
|
Ok(response) => match response.goal.as_ref() {
|
||||||
self.show_replace_thread_goal_confirmation(thread_id, objective);
|
Some(goal) if should_confirm_before_replacing_goal(goal) => {
|
||||||
return;
|
self.show_replace_thread_goal_confirmation(thread_id, objective);
|
||||||
}
|
return;
|
||||||
Ok(_) => {}
|
}
|
||||||
|
Some(_) => ThreadGoalSetMode::ReplaceExisting,
|
||||||
|
None => mode,
|
||||||
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.chat_widget
|
self.chat_widget
|
||||||
.add_error_message(thread_goal_error_message("read", &err));
|
.add_error_message(thread_goal_error_message("read", &err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
mode
|
||||||
|
};
|
||||||
|
|
||||||
let replacing_goal = matches!(mode, ThreadGoalSetMode::ReplaceExisting);
|
let replacing_goal = matches!(mode, ThreadGoalSetMode::ReplaceExisting);
|
||||||
if replacing_goal {
|
if replacing_goal {
|
||||||
@@ -296,6 +302,19 @@ fn is_ephemeral_thread_goal_error(err: &color_eyre::Report) -> bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_confirm_before_replacing_goal(goal: &ThreadGoal) -> bool {
|
||||||
|
// Completed goals are terminal, so `/goal <objective>` can start a fresh goal
|
||||||
|
// without asking the user to confirm replacing already-finished work.
|
||||||
|
match goal.status {
|
||||||
|
ThreadGoalStatus::Complete => false,
|
||||||
|
ThreadGoalStatus::Active
|
||||||
|
| ThreadGoalStatus::Paused
|
||||||
|
| ThreadGoalStatus::Blocked
|
||||||
|
| ThreadGoalStatus::UsageLimited
|
||||||
|
| ThreadGoalStatus::BudgetLimited => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::history_cell::HistoryCell;
|
use crate::history_cell::HistoryCell;
|
||||||
@@ -350,4 +369,37 @@ mod tests {
|
|||||||
"Failed to read thread goal: thread/goal/get failed in TUI"
|
"Failed to read thread goal: thread/goal/get failed in TUI"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completed_goal_does_not_require_replace_confirmation() {
|
||||||
|
assert!(!should_confirm_before_replacing_goal(&test_goal(
|
||||||
|
ThreadGoalStatus::Complete
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unfinished_goals_require_replace_confirmation() {
|
||||||
|
for status in [
|
||||||
|
ThreadGoalStatus::Active,
|
||||||
|
ThreadGoalStatus::Paused,
|
||||||
|
ThreadGoalStatus::Blocked,
|
||||||
|
ThreadGoalStatus::UsageLimited,
|
||||||
|
ThreadGoalStatus::BudgetLimited,
|
||||||
|
] {
|
||||||
|
assert!(should_confirm_before_replacing_goal(&test_goal(status)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_goal(status: ThreadGoalStatus) -> ThreadGoal {
|
||||||
|
ThreadGoal {
|
||||||
|
thread_id: ThreadId::new().to_string(),
|
||||||
|
objective: "Finish the thing.".to_string(),
|
||||||
|
status,
|
||||||
|
token_budget: None,
|
||||||
|
tokens_used: 0,
|
||||||
|
time_used_seconds: 0,
|
||||||
|
created_at: 1_776_272_400,
|
||||||
|
updated_at: 1_776_272_460,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user