Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,11 @@ pub(crate) struct ChatWidget {
// This gates rendering of the "Worked for …" separator so purely conversational turns don't
// show an empty divider. It is reset when the separator is emitted.
had_work_activity: bool,
// Status-indicator elapsed seconds captured at the last emitted final-message separator.
//
// This lets the separator show per-chunk work time (since the previous separator) rather than
// the total task-running time reported by the status indicator.
last_separator_elapsed_secs: Option<u64>,

last_rendered_width: std::cell::Cell<Option<usize>>,
// Feedback sink for /feedback
Expand Down Expand Up @@ -1528,7 +1533,8 @@ impl ChatWidget {
let elapsed_seconds = self
.bottom_pane
.status_widget()
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds);
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds)
.map(|current| self.worked_elapsed_from(current));
self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds));
self.needs_final_message_separator = false;
self.had_work_activity = false;
Expand All @@ -1548,6 +1554,17 @@ impl ChatWidget {
self.request_redraw();
}

fn worked_elapsed_from(&mut self, current_elapsed: u64) -> u64 {
let baseline = match self.last_separator_elapsed_secs {
Some(last) if current_elapsed < last => 0,
Some(last) => last,
None => 0,
};
let elapsed = current_elapsed.saturating_sub(baseline);
self.last_separator_elapsed_secs = Some(current_elapsed);
elapsed
}

pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {
let running = self.running_commands.remove(&ev.call_id);
if self.suppressed_exec_calls.remove(&ev.call_id) {
Expand Down Expand Up @@ -1884,6 +1901,7 @@ impl ChatWidget {
pre_review_token_info: None,
needs_final_message_separator: false,
had_work_activity: false,
last_separator_elapsed_secs: None,
last_rendered_width: std::cell::Cell::new(None),
feedback,
current_rollout_path: None,
Expand Down Expand Up @@ -1998,6 +2016,7 @@ impl ChatWidget {
pre_review_token_info: None,
needs_final_message_separator: false,
had_work_activity: false,
last_separator_elapsed_secs: None,
last_rendered_width: std::cell::Cell::new(None),
feedback,
current_rollout_path: None,
Expand Down
11 changes: 11 additions & 0 deletions codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ async fn make_chatwidget_manual(
pre_review_token_info: None,
needs_final_message_separator: false,
had_work_activity: false,
last_separator_elapsed_secs: None,
last_rendered_width: std::cell::Cell::new(None),
feedback: codex_feedback::CodexFeedback::new(),
current_rollout_path: None,
Expand Down Expand Up @@ -847,6 +848,16 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) {
));
}

#[tokio::test]
async fn worked_elapsed_from_resets_when_timer_restarts() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
assert_eq!(chat.worked_elapsed_from(5), 5);
assert_eq!(chat.worked_elapsed_from(9), 4);
// Simulate status timer resetting (e.g., status indicator recreated for a new task).
assert_eq!(chat.worked_elapsed_from(3), 3);
assert_eq!(chat.worked_elapsed_from(7), 4);
}

pub(crate) async fn make_chatwidget_manual_with_sender() -> (
ChatWidget,
AppEventSender,
Expand Down
21 changes: 20 additions & 1 deletion codex-rs/tui2/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,11 @@ pub(crate) struct ChatWidget {
// This gates rendering of the "Worked for …" separator so purely conversational turns don't
// show an empty divider. It is reset when the separator is emitted.
had_work_activity: bool,
// Status-indicator elapsed seconds captured at the last emitted final-message separator.
//
// This lets the separator show per-chunk work time (since the previous separator) rather than
// the total task-running time reported by the status indicator.
last_separator_elapsed_secs: Option<u64>,

last_rendered_width: std::cell::Cell<Option<usize>>,
// Feedback sink for /feedback
Expand Down Expand Up @@ -1330,7 +1335,8 @@ impl ChatWidget {
let elapsed_seconds = self
.bottom_pane
.status_widget()
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds);
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds)
.map(|current| self.worked_elapsed_from(current));
self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds));
self.needs_final_message_separator = false;
self.had_work_activity = false;
Expand All @@ -1353,6 +1359,17 @@ impl ChatWidget {
}
}

fn worked_elapsed_from(&mut self, current_elapsed: u64) -> u64 {
let baseline = match self.last_separator_elapsed_secs {
Some(last) if current_elapsed < last => 0,
Some(last) => last,
None => 0,
};
let elapsed = current_elapsed.saturating_sub(baseline);
self.last_separator_elapsed_secs = Some(current_elapsed);
elapsed
}

pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {
let running = self.running_commands.remove(&ev.call_id);
if self.suppressed_exec_calls.remove(&ev.call_id) {
Expand Down Expand Up @@ -1686,6 +1703,7 @@ impl ChatWidget {
pre_review_token_info: None,
needs_final_message_separator: false,
had_work_activity: false,
last_separator_elapsed_secs: None,
last_rendered_width: std::cell::Cell::new(None),
feedback,
current_rollout_path: None,
Expand Down Expand Up @@ -1798,6 +1816,7 @@ impl ChatWidget {
pre_review_token_info: None,
needs_final_message_separator: false,
had_work_activity: false,
last_separator_elapsed_secs: None,
last_rendered_width: std::cell::Cell::new(None),
feedback,
current_rollout_path: None,
Expand Down
11 changes: 11 additions & 0 deletions codex-rs/tui2/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ async fn make_chatwidget_manual(
pre_review_token_info: None,
needs_final_message_separator: false,
had_work_activity: false,
last_separator_elapsed_secs: None,
last_rendered_width: std::cell::Cell::new(None),
feedback: codex_feedback::CodexFeedback::new(),
current_rollout_path: None,
Expand Down Expand Up @@ -833,6 +834,16 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) {
));
}

#[tokio::test]
async fn worked_elapsed_from_resets_when_timer_restarts() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
assert_eq!(chat.worked_elapsed_from(5), 5);
assert_eq!(chat.worked_elapsed_from(9), 4);
// Simulate status timer resetting (e.g., status indicator recreated for a new task).
assert_eq!(chat.worked_elapsed_from(3), 3);
assert_eq!(chat.worked_elapsed_from(7), 4);
}

pub(crate) async fn make_chatwidget_manual_with_sender() -> (
ChatWidget,
AppEventSender,
Expand Down
Loading