aboutsummaryrefslogtreecommitdiff
path: root/src/detect/agents/codex.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 04:40:56 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 04:40:56 +0300
commite8acc76ef6d6c587a2b0c500ce02699344b9fe20 (patch)
treed1b016fdbb16cb09e9dee2484057a4c0cb6625c7 /src/detect/agents/codex.rs
parent580db93eccc9f03cdc31841e294b8c18855013f6 (diff)
fix: detect truncated codex status rows
Diffstat (limited to 'src/detect/agents/codex.rs')
-rw-r--r--src/detect/agents/codex.rs45
1 files changed, 42 insertions, 3 deletions
diff --git a/src/detect/agents/codex.rs b/src/detect/agents/codex.rs
index a53d9358..0a9bdadb 100644
--- a/src/detect/agents/codex.rs
+++ b/src/detect/agents/codex.rs
@@ -135,8 +135,7 @@ fn codex_live_working_line(line: &str) -> bool {
let lower = trimmed.to_lowercase();
codex_working_status_line(line)
&& (trimmed.contains("Waiting for background terminal")
- || lower.contains("esc to interrupt")
- || lower.contains("esc…")
+ || has_codex_status_interrupt_hint(&lower)
|| lower.contains("background terminal running")
|| lower.contains("/ps to view")
|| lower.contains("/stop to close"))
@@ -152,12 +151,52 @@ fn codex_working_status_line(line: &str) -> bool {
trimmed.starts_with('•')
&& (trimmed.contains("Working (")
|| trimmed.contains("Waiting for background terminal (")
- || lower.contains(" • esc to interrupt")
+ || has_codex_status_interrupt_hint(&lower)
|| lower.contains("reviewing approval request (")
|| (lower.contains("reviewing ") && lower.contains(" approval requests ("))
|| trimmed.contains("Booting MCP server:"))
}
+fn has_codex_status_interrupt_hint(lower_line: &str) -> bool {
+ let Some((before_escape, after_escape)) = lower_line.split_once(" • esc") else {
+ return false;
+ };
+
+ has_codex_status_elapsed(before_escape) && has_codex_status_escape_suffix(after_escape)
+}
+
+fn has_codex_status_elapsed(before_escape: &str) -> bool {
+ let Some((_, elapsed)) = before_escape.rsplit_once('(') else {
+ return false;
+ };
+
+ let parts: Vec<&str> = elapsed.split_whitespace().collect();
+ (1..=3).contains(&parts.len())
+ && parts.iter().all(|part| {
+ part.len() >= 2
+ && matches!(part.as_bytes().last(), Some(b'h' | b'm' | b's'))
+ && part[..part.len() - 1].chars().all(|ch| ch.is_ascii_digit())
+ })
+}
+
+fn has_codex_status_escape_suffix(after_escape: &str) -> bool {
+ let suffix = after_escape.trim_start();
+ if suffix.starts_with('…') || suffix.starts_with("to interrupt") {
+ return true;
+ }
+
+ let Some(rest) = suffix.strip_prefix("to ") else {
+ return false;
+ };
+ if rest.starts_with('…') {
+ return true;
+ }
+
+ rest.split_once('…')
+ .map(|(fragment, _)| !fragment.is_empty() && "interrupt".starts_with(fragment.trim_end()))
+ .unwrap_or(false)
+}
+
fn has_codex_current_prompt(content: &str) -> bool {
codex_current_prompt_region(content).is_some()
}