From 9c96f7ddb3be2cc575a159d4d1f1d49fb10d7006 Mon Sep 17 00:00:00 2001 From: JJ Liebig Date: Thu, 24 Sep 2026 02:23:42 +0200 Subject: fix: avoid inferring codex idle from terminal output (#4563) refs #4507 --- src/pane/agent_detection.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) (limited to 'src/pane') diff --git a/src/pane/agent_detection.rs b/src/pane/agent_detection.rs index 9ef10dfe..e37aec3b 100644 --- a/src/pane/agent_detection.rs +++ b/src/pane/agent_detection.rs @@ -89,7 +89,9 @@ pub(super) struct IdleScreenScanSkipInput { } pub(super) fn should_skip_idle_screen_scan(input: IdleScreenScanSkipInput) -> bool { - if input.state != AgentState::Idle + let stable_state = input.state == AgentState::Idle + || (input.state == AgentState::Unknown && input.agent == Some(Agent::Codex)); + if !stable_state || input.agent.is_none() || input.pending_idle_active || input.agent_changed @@ -316,6 +318,23 @@ pub(super) fn detection_update_for_publish_with_osc( (!detection.skip_state_update).then_some(detection) } +pub(super) fn codex_prompt_ready(content: &str) -> bool { + // The composer can remain visible during a turn; this is startup evidence only. + let recent: String = content + .lines() + .rev() + .take(12) + .collect::>() + .into_iter() + .rev() + .flat_map(str::chars) + .filter(|ch| !ch.is_whitespace()) + .collect(); + recent.contains("›AskCodextodoanything") + && !recent.contains("model:loading") + && !recent.contains("Resumingsession") +} + pub(super) fn observe_detection_content_change(bytes: &[u8], detection_content_seq: &AtomicU64) { if !bytes.is_empty() { detection_content_seq.fetch_add(1, Ordering::Relaxed); @@ -379,6 +398,13 @@ mod tests { } } + #[test] + fn codex_startup_prompt_survives_terminal_wraps() { + let wrapped = "header\n› Ask Codex to do\nanything\nfooter"; + assert!(codex_prompt_ready(wrapped)); + assert!(!codex_prompt_ready(&format!("model: load\ning\n{wrapped}"))); + } + #[test] fn screen_read_skips_unchanged_idle_bottom_buffer() { assert_eq!( @@ -387,6 +413,32 @@ mod tests { ); } + #[test] + fn screen_read_skips_unchanged_ambiguous_codex_but_not_new_content_or_replacement() { + let mut input = screen_read_input(AgentState::Unknown, 10); + assert_eq!( + decide_detection_screen_read(input), + DetectionScreenReadDecision::Skip + ); + input.current_detection_content_seq = Some(11); + assert_eq!( + decide_detection_screen_read(input), + DetectionScreenReadDecision::Read + ); + input.current_detection_content_seq = Some(10); + input.agent_changed = true; + assert_eq!( + decide_detection_screen_read(input), + DetectionScreenReadDecision::Read + ); + input.agent_changed = false; + input.agent = Some(Agent::Pi); + assert_eq!( + decide_detection_screen_read(input), + DetectionScreenReadDecision::Read + ); + } + #[test] fn screen_read_reads_when_idle_bottom_buffer_changes() { assert_eq!( -- cgit v1.2.3