aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-09 16:53:00 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-09 17:28:02 +0300
commit949597957323d9883868cc12d2ce72c2117e835e (patch)
tree0efbd81cc3a55aab777adf9a2c1a4e8342a2641a /src
parent1cbd3129363d2ad867d2753ef176a944554591f6 (diff)
fix: harden codex detection for narrow panes
Diffstat (limited to 'src')
-rw-r--r--src/detect.rs34
1 files changed, 28 insertions, 6 deletions
diff --git a/src/detect.rs b/src/detect.rs
index b7ffa441..11b0f365 100644
--- a/src/detect.rs
+++ b/src/detect.rs
@@ -1,6 +1,6 @@
-//! Agent state detection via screen content pattern matching.
+//! Agent state detection via terminal tail pattern matching.
//!
-//! Each pane's visible terminal content is read periodically and matched
+//! Each pane's live bottom-of-buffer text is read periodically and matched
//! against known agent output patterns to determine state.
/// The detected state of a terminal pane.
@@ -72,7 +72,7 @@ pub fn identify_agent_in_job(job: &crate::platform::ForegroundJob) -> Option<(Ag
best.map(|(_, agent, name)| (agent, name))
}
-/// Detect the state of an agent from the visible screen content.
+/// Detect the state of an agent from the live terminal tail snapshot.
/// If `agent` is `None`, returns `Unknown`.
pub fn detect_state(agent: Option<Agent>, screen_content: &str) -> AgentState {
let Some(agent) = agent else {
@@ -166,7 +166,7 @@ fn detect_codex(content: &str) -> AgentState {
// Blocked patterns
if lower.contains("press enter to confirm or esc to cancel")
- || lower.contains("| enter to submit answer")
+ || lower.contains("enter to submit answer")
|| lower.contains("allow command?")
|| lower.contains("[y/n]")
|| lower.contains("yes (y)")
@@ -178,7 +178,7 @@ fn detect_codex(content: &str) -> AgentState {
}
// Working
- if has_interrupt_pattern(&lower) {
+ if has_interrupt_pattern(&lower) || has_codex_working_header(content) {
return AgentState::Working;
}
@@ -453,6 +453,13 @@ fn has_spinner_activity(content: &str) -> bool {
false
}
+fn has_codex_working_header(content: &str) -> bool {
+ content.lines().any(|line| {
+ let trimmed = line.trim_start();
+ trimmed.starts_with('•') && trimmed.contains("Working (")
+ })
+}
+
/// Cursor spinner: ⬡ or ⬢ followed by a word ending in "ing"
fn has_cursor_spinner(content: &str) -> bool {
for line in content.lines() {
@@ -721,6 +728,16 @@ mod tests {
}
#[test]
+ fn codex_waiting_submit_answer_wrapped_footer() {
+ assert_eq!(
+ detect_codex(
+ "Question 1/2\nChoose an option.\nenter to submit answer\nesc to interrupt"
+ ),
+ AgentState::Blocked
+ );
+ }
+
+ #[test]
fn codex_working_interrupt() {
assert_eq!(
detect_codex("generating code\nesc to interrupt"),
@@ -729,6 +746,11 @@ mod tests {
}
#[test]
+ fn codex_working_truncated_status_header() {
+ assert_eq!(detect_codex("• Working (0s • esc…"), AgentState::Working);
+ }
+
+ #[test]
fn codex_idle() {
assert_eq!(detect_codex("❯ "), AgentState::Idle);
}
@@ -1178,7 +1200,7 @@ mod tests {
}
#[test]
- fn visible_screen_content_works_with_detection() {
+ fn terminal_tail_content_works_with_detection() {
assert_eq!(detect_pi("Working..."), AgentState::Working);
}