merge code-branch-20250929-114830 via /merge

This commit is contained in:
James Peter
2025-09-30 08:14:56 +10:00
parent 29ff2a0c9b
commit 9e622abd56
4 changed files with 99 additions and 27 deletions
@@ -38,8 +38,8 @@ pub(crate) struct AutoCoordinatorViewModel {
pub status_lines: Vec<String>,
pub prompt: Option<String>,
pub awaiting_submission: bool,
#[allow(dead_code)]
pub waiting_for_response: bool,
pub coordinator_waiting: bool,
pub countdown: Option<CountdownState>,
pub button: Option<AutoCoordinatorButton>,
pub manual_hint: Option<String>,
@@ -173,17 +173,33 @@ impl AutoCoordinatorView {
}
fn status_lines(&self) -> Vec<Line<'static>> {
let frame_idx = DRIVE_SPINNER_TICK.fetch_add(1, Ordering::Relaxed);
let spinner_symbol = DRIVE_SPINNER_FRAMES[frame_idx % DRIVE_SPINNER_FRAMES.len()].to_string();
let spinner_symbol = if self.model.coordinator_waiting {
let frame_idx = DRIVE_SPINNER_TICK.fetch_add(1, Ordering::Relaxed);
Some(DRIVE_SPINNER_FRAMES[frame_idx % DRIVE_SPINNER_FRAMES.len()].to_string())
} else {
None
};
let mut lines: Vec<Line<'static>> = Vec::new();
for (index, (text, style)) in self.derived_status_entries().into_iter().enumerate() {
if index == 0 {
lines.push(Line::from(vec![
Span::styled(spinner_symbol.clone(), Style::default().fg(colors::spinner()).add_modifier(Modifier::BOLD)),
Span::raw(" "),
Span::styled(text, style),
]));
if let Some(symbol) = spinner_symbol.as_ref() {
lines.push(Line::from(vec![
Span::styled(
symbol.clone(),
Style::default()
.fg(colors::spinner())
.add_modifier(Modifier::BOLD),
),
Span::raw(" "),
Span::styled(text, style),
]));
} else {
lines.push(Line::from(vec![
Span::raw(" "),
Span::styled(text, style),
]));
}
} else {
lines.push(Line::from(Span::styled(text, style)));
}
@@ -279,7 +295,7 @@ impl AutoCoordinatorView {
};
let awaiting = self.model.awaiting_submission;
let spinner_active = !awaiting && self.model.waiting_for_response;
let spinner_active = !awaiting && self.model.coordinator_waiting;
let mut total = 0;
@@ -358,7 +374,7 @@ impl AutoCoordinatorView {
return;
}
let spinner_active = !self.model.awaiting_submission && self.model.waiting_for_response;
let spinner_active = !self.model.awaiting_submission && self.model.coordinator_waiting;
let mut lines: Vec<Line<'static>> = Vec::new();
+8 -17
View File
@@ -473,6 +473,7 @@ struct AutoCoordinatorUiState {
last_decision_display: Option<String>,
observer_telemetry: Option<AutoObserverTelemetry>,
observer_status: AutoObserverStatus,
coordinator_waiting: bool,
}
impl AutoCoordinatorUiState {
@@ -9771,6 +9772,7 @@ impl ChatWidget<'_> {
self.auto_state.last_broadcast_thought = None;
self.auto_state.seconds_remaining = AUTO_COUNTDOWN_SECONDS;
self.auto_state.waiting_for_response = true;
self.auto_state.coordinator_waiting = true;
self.update_header_border_activation();
self.auto_rebuild_live_ring();
self.push_background_tail(format!("Auto Drive started: {goal_text}"));
@@ -9797,11 +9799,10 @@ impl ChatWidget<'_> {
self.auto_stop(Some("Coordinator stopped unexpectedly.".to_string()));
} else {
self.auto_state.waiting_for_response = true;
self.auto_state.coordinator_waiting = true;
self.auto_state.current_thoughts = None;
self.auto_state.last_broadcast_thought = None;
self.auto_state.current_display_line = None;
self.auto_state.current_summary_index = None;
self.auto_state.last_decision_display = None;
self.auto_state.placeholder_phrase =
Some(auto_drive_strings::next_auto_drive_phrase().to_string());
self.auto_state.thinking_prefix_stripped = false;
@@ -9821,6 +9822,7 @@ impl ChatWidget<'_> {
return;
}
self.auto_state.coordinator_waiting = false;
self.auto_on_reasoning_final(&thoughts);
self.auto_state.last_decision_display = self.auto_state.current_display_line.clone();
self.auto_state.paused_for_manual_edit = false;
@@ -9983,6 +9985,7 @@ impl ChatWidget<'_> {
self.auto_state.awaiting_submission = false;
self.auto_state.waiting_for_response = true;
self.auto_state.coordinator_waiting = false;
self.auto_state.paused_for_manual_edit = false;
self.auto_state.resume_after_manual_submit = false;
self.auto_state.seconds_remaining = 0;
@@ -10064,16 +10067,15 @@ impl ChatWidget<'_> {
return;
}
self.auto_state.waiting_for_response = false;
self.auto_state.coordinator_waiting = false;
self.auto_state.awaiting_submission = false;
self.auto_state.paused_for_manual_edit = false;
self.auto_state.resume_after_manual_submit = false;
self.auto_state.seconds_remaining = AUTO_COUNTDOWN_SECONDS;
self.auto_state.current_thoughts = Some(String::new());
self.auto_state.current_display_line = None;
self.auto_state.current_summary_index = None;
self.auto_state.placeholder_phrase = None;
self.auto_state.thinking_prefix_stripped = false;
self.auto_state.last_decision_display = None;
self.update_header_border_activation();
self.auto_rebuild_live_ring();
self.request_redraw();
@@ -10183,6 +10185,7 @@ impl ChatWidget<'_> {
prompt,
awaiting_submission: self.auto_state.awaiting_submission,
waiting_for_response: self.auto_state.waiting_for_response,
coordinator_waiting: self.auto_state.coordinator_waiting,
countdown,
button,
manual_hint,
@@ -10267,10 +10270,7 @@ impl ChatWidget<'_> {
if self.auto_state.current_summary_index != Some(idx) {
self.auto_state.current_summary_index = Some(idx);
self.auto_state.current_thoughts = Some(String::new());
self.auto_state.current_display_line = None;
self.auto_state.last_broadcast_thought = None;
self.auto_state.thinking_prefix_stripped = false;
self.auto_state.last_decision_display = None;
}
}
@@ -10288,7 +10288,7 @@ impl ChatWidget<'_> {
self.auto_state.thinking_prefix_stripped = true;
}
let payload = {
{
let entry = self
.auto_state
.current_thoughts
@@ -10299,15 +10299,6 @@ impl ChatWidget<'_> {
}
entry.push_str(&cleaned_delta);
entry.clone()
};
self.auto_update_display_title();
self.auto_broadcast_thoughts(&payload);
if self.auto_state.waiting_for_response {
self.auto_rebuild_live_ring();
self.request_redraw();
}
}
@@ -64,6 +64,14 @@ impl AutoCoordinatorHandle {
pub fn cancel(&self) {
self.cancel_token.cancel();
}
#[cfg(test)]
pub(super) fn for_tests(tx: Sender<AutoCoordinatorCommand>) -> Self {
Self {
tx,
cancel_token: CancellationToken::new(),
}
}
}
#[derive(Debug)]
+57
View File
@@ -3,6 +3,7 @@
use super::*;
use crate::app_event::{
AppEvent,
AutoCoordinatorStatus,
AutoObserverStatus,
AutoObserverTelemetry,
BackgroundPlacement,
@@ -10,6 +11,7 @@ use crate::app_event::{
use crate::app_event_sender::AppEventSender;
use crate::history::state::{ExecStatus, ExploreEntryStatus, HistoryId, HistoryRecord, HistoryState};
use crate::slash_command::SlashCommand;
use super::auto_coordinator::AutoCoordinatorHandle;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConfigToml;
@@ -1000,6 +1002,61 @@ fn agents_terminal_esc_closes_from_sidebar() {
assert!(!chat.agents_terminal.active, "Esc should close from sidebar focus");
}
#[test]
fn auto_thinking_keeps_previous_display_until_decision() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
chat.auto_state.active = true;
chat.auto_state.waiting_for_response = true;
chat.auto_state.coordinator_waiting = true;
chat.auto_state.current_display_line = Some("Prev thought".to_string());
chat.auto_state.current_thoughts = Some("Prev thought".to_string());
chat.auto_handle_thinking("**Next:** run lint".to_string(), Some(3));
assert_eq!(chat.auto_state.current_summary_index, Some(3));
assert_eq!(
chat.auto_state.current_display_line.as_deref(),
Some("Prev thought")
);
chat.auto_handle_thinking("extra detail".to_string(), None);
assert_eq!(
chat.auto_state.current_display_line.as_deref(),
Some("Prev thought")
);
}
#[test]
fn auto_decision_persists_thought_through_cli_cycle() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
chat.auto_state.active = true;
chat.auto_state.waiting_for_response = true;
chat.auto_state.coordinator_waiting = true;
chat.auto_handle_decision(
AutoCoordinatorStatus::Continue,
"**Plan:** run tests".to_string(),
Some("run tests".to_string()),
);
assert_eq!(chat.auto_state.current_display_line.as_deref(), Some("Plan:"));
assert!(!chat.auto_state.coordinator_waiting, "spinner should stop");
assert!(!chat.auto_state.waiting_for_response, "coordinator finished");
chat.auto_submit_prompt();
assert!(chat.auto_state.waiting_for_response, "waiting on CLI");
assert!(!chat.auto_state.coordinator_waiting, "spinner stays off during CLI");
assert_eq!(chat.auto_state.current_display_line.as_deref(), Some("Plan:"));
chat.auto_state.placeholder_phrase = None;
let (tx, _rx_handle) = channel();
chat.auto_handle = Some(AutoCoordinatorHandle::for_tests(tx));
chat.auto_on_assistant_final();
assert!(chat.auto_state.waiting_for_response, "next JSON in flight");
assert!(chat.auto_state.coordinator_waiting, "spinner resumes for JSON");
assert_eq!(chat.auto_state.current_display_line.as_deref(), Some("Plan:"));
}
fn pump_app_events(
chat: &mut ChatWidget<'_>,
rx: &std::sync::mpsc::Receiver<AppEvent>,