diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-08 04:40:56 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-08 04:40:56 +0300 |
| commit | e8acc76ef6d6c587a2b0c500ce02699344b9fe20 (patch) | |
| tree | d1b016fdbb16cb09e9dee2484057a4c0cb6625c7 /src/detect/agents/codex.rs | |
| parent | 580db93eccc9f03cdc31841e294b8c18855013f6 (diff) | |
fix: detect truncated codex status rows
Diffstat (limited to 'src/detect/agents/codex.rs')
| -rw-r--r-- | src/detect/agents/codex.rs | 45 |
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() } |
