aboutsummaryrefslogtreecommitdiff
path: root/src/detect
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-24 02:23:42 +0200
committerGitHub <noreply@github.com>2026-09-24 02:23:42 +0200
commit9c96f7ddb3be2cc575a159d4d1f1d49fb10d7006 (patch)
tree182e89183981160397af301c075ab11316b15850 /src/detect
parent21fd121a63db53ff44b947bbe38c47bbf35e632c (diff)
fix: avoid inferring codex idle from terminal output (#4563)
refs #4507
Diffstat (limited to 'src/detect')
-rw-r--r--src/detect/manifest.rs27
-rw-r--r--src/detect/manifest/tests.rs20
-rw-r--r--src/detect/manifests/codex.toml16
-rw-r--r--src/detect/mod.rs4
4 files changed, 21 insertions, 46 deletions
diff --git a/src/detect/manifest.rs b/src/detect/manifest.rs
index acd6b8f9..e224adfe 100644
--- a/src/detect/manifest.rs
+++ b/src/detect/manifest.rs
@@ -416,23 +416,6 @@ pub fn explain_for_label(agent_label: &str, screen_content: &str) -> DetectionEx
explain(agent, screen_content)
}
-pub fn should_skip_state_update(agent: Agent, screen_content: &str) -> bool {
- let Some(loaded) = load_manifest(agent) else {
- return false;
- };
- evaluate_loaded_manifest(
- agent,
- DetectionInput {
- screen: screen_content,
- osc_title: "",
- osc_progress: "",
- },
- loaded,
- false,
- )
- .skip_state_update
-}
-
impl DetectionExplain {
fn into_detection(self) -> AgentDetection {
AgentDetection {
@@ -557,14 +540,14 @@ fn fallback_explain(
)
})
.unwrap_or((None, Vec::new(), None, None, None, false));
- let known_agent = agent.is_some();
+ let assume_idle = agent.is_some_and(|agent| agent != Agent::Codex);
let remote_update_status = include_update_status
.then(|| agent.and_then(remote_update_status))
.flatten();
DetectionExplain {
agent: agent.map(|agent| agent_label(agent).to_string()),
- state: if known_agent {
+ state: if assume_idle {
AgentState::Idle
} else {
AgentState::Unknown
@@ -577,7 +560,11 @@ fn fallback_explain(
visible_working: false,
skip_state_update: false,
skipped_update_reason: None,
- fallback_reason: known_agent.then(|| DEFAULT_KNOWN_AGENT_IDLE_FALLBACK.to_string()),
+ fallback_reason: match agent {
+ Some(Agent::Codex) => Some("codex_state_ambiguous".to_string()),
+ Some(_) => Some(DEFAULT_KNOWN_AGENT_IDLE_FALLBACK.to_string()),
+ None => None,
+ },
evaluated_rules,
warning,
manifest_version,
diff --git a/src/detect/manifest/tests.rs b/src/detect/manifest/tests.rs
index 07e9aa38..1e6b1a85 100644
--- a/src/detect/manifest/tests.rs
+++ b/src/detect/manifest/tests.rs
@@ -89,15 +89,21 @@ fn write_local_codex(content: &str) {
}
#[test]
-fn known_agent_no_match_defaults_to_idle_fallback() {
+fn codex_no_match_is_unknown_without_changing_other_agents() {
with_manifest_dirs("no-match", || {
write_local_codex(&local_manifest("working", "active-marker"));
let explain = explain(Agent::Codex, "unmatched-marker");
- assert_eq!(explain.state, AgentState::Idle);
+ assert_eq!(explain.state, AgentState::Unknown);
assert!(!explain.visible_idle);
assert_eq!(
explain.fallback_reason.as_deref(),
+ Some("codex_state_ambiguous")
+ );
+ let other = fallback_explain(Some(Agent::Pi), None, false);
+ assert_eq!(other.state, AgentState::Idle);
+ assert_eq!(
+ other.fallback_reason.as_deref(),
Some(DEFAULT_KNOWN_AGENT_IDLE_FALLBACK)
);
});
@@ -184,10 +190,10 @@ fn fallback_explain_preserves_active_manifest_version() {
let explain = explain(Agent::Codex, "ordinary prompt text");
- assert_eq!(explain.state, AgentState::Idle);
+ assert_eq!(explain.state, AgentState::Unknown);
assert_eq!(
explain.fallback_reason.as_deref(),
- Some(DEFAULT_KNOWN_AGENT_IDLE_FALLBACK)
+ Some("codex_state_ambiguous")
);
assert_eq!(explain.manifest_version.as_deref(), Some("9999.01.01.1"));
assert!(matches!(
@@ -267,10 +273,10 @@ fn detection_uses_cached_manifest_until_explicit_reload() {
write_remote_codex_without_reload(&remote_manifest("9999.01.01.2", "working", "new-ready"));
let unchanged = explain(Agent::Codex, "new-ready");
- assert_eq!(unchanged.state, AgentState::Idle);
+ assert_eq!(unchanged.state, AgentState::Unknown);
assert_eq!(
unchanged.fallback_reason.as_deref(),
- Some(DEFAULT_KNOWN_AGENT_IDLE_FALLBACK)
+ Some("codex_state_ambiguous")
);
assert_eq!(
unchanged.cached_remote_version.as_deref(),
@@ -479,8 +485,6 @@ contains = ["overlay-marker"]
assert!(!result.visible_working);
assert!(!result.visible_blocker);
assert!(detect(Agent::Codex, screen).skip_state_update);
- assert!(should_skip_state_update(Agent::Codex, screen));
- assert!(!should_skip_state_update(Agent::Codex, "activity-marker"));
});
}
diff --git a/src/detect/manifests/codex.toml b/src/detect/manifests/codex.toml
index ecef12b7..dd275758 100644
--- a/src/detect/manifests/codex.toml
+++ b/src/detect/manifests/codex.toml
@@ -1,7 +1,7 @@
id = "codex"
-version = "2026.09.22.1"
+version = "2026.09.23.1"
min_engine_version = 3
-updated_at = "2026-09-22T00:00:00Z"
+updated_at = "2026-09-23T22:12:00Z"
[[rules]]
id = "osc_title_blocked"
@@ -92,15 +92,3 @@ any = [{ contains = [" to interrupt)"] }, { contains = ["s)"] }]
regex = ['(?m)^(?:[•◦][ \t]+)?[^\s›•◦■✗✓─][^\r\n]* \((?:[0-9]+[hm] )*[0-9]+s(?: • [^\r\n]+? to interrupt)?\)(?: · [^\r\n]*)?(?:\r?\n(?:[^•◦›■✗✓─\r\n][^\r\n]*|•[ \t]+(?:Queued\s+follow-up\s+inputs|Messages\s+to\s+be\s+submitted\s+after\s+next\s+tool\s+call(?:\s+\(press\s+[^\r\n]+?\s+to\s+interrupt\s+and\s+send\s+immediately\))?|Messages\s+to\s+be\s+submitted\s+at\s+end\s+of\s+turn)|›[⠁⠂⠄⠈⠐⠠⡀⢀][^\r\n]*)?)*\s*\z']
# A failed reconnect keeps its final elapsed timer but is no longer working.
not = [{ line_regex = ['^(?:[•◦][ \t]+)?Reconnect failed — check the endpoint, then relaunch \([0-9hms ]+\)$'] }]
-
-[[rules]]
-id = "osc_title_idle"
-state = "idle"
-priority = 100
-region = "osc_title"
-visible_idle = true
-regex = ['\S']
-not = [
- { regex = ['(?:^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏](?: |$)'] },
- { contains = ["Action Required"] },
-]
diff --git a/src/detect/mod.rs b/src/detect/mod.rs
index 3dae571b..408ee669 100644
--- a/src/detect/mod.rs
+++ b/src/detect/mod.rs
@@ -320,10 +320,6 @@ pub fn detect_agent_with_osc(
)
}
-pub fn should_skip_state_update(agent: Option<Agent>, screen_content: &str) -> bool {
- agent.is_some_and(|agent| manifest::should_skip_state_update(agent, screen_content))
-}
-
pub(crate) fn full_lifecycle_hook_authority(source: &str, agent_label: &str) -> bool {
matches!(
(source, agent_label),