aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-09 03:10:44 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-09 04:26:19 +0300
commit120001b591e64d80a557d81e3b112460b9987fc6 (patch)
treebcdf3e71ca2818d76e53f94d1be685e31d4ce6be /src
parent55f5a3b5d4ab90b7df207531d3cd3cd5f0923616 (diff)
fix: keep non-claude pty working transitions screenless
Diffstat (limited to 'src')
-rw-r--r--src/pane.rs97
-rw-r--r--src/pane/agent_detection.rs126
2 files changed, 215 insertions, 8 deletions
diff --git a/src/pane.rs b/src/pane.rs
index f1d1c699..9fcddce0 100644
--- a/src/pane.rs
+++ b/src/pane.rs
@@ -31,10 +31,11 @@ mod xtgettcap;
use self::agent_detection::{
agent_caused_pty_activity_active, baseline_pty_causality, decide_detection_screen_read,
- decide_screen_detection_publish, detection_update_for_publish, handle_skipped_detection_update,
- observe_pty_output_activity, DetectionPublishDecision, DetectionScreenReadDecision,
- DetectionScreenReadInput, PendingIdleConfirmation, PendingWorkingConfirmation,
- PostTaintWorkingLease, PtyCausalityTracker, ScreenDetectionPublishInput,
+ decide_pty_working_publish_without_screen, decide_screen_detection_publish,
+ detection_update_for_publish, handle_skipped_detection_update, observe_pty_output_activity,
+ DetectionPublishDecision, DetectionScreenReadDecision, DetectionScreenReadInput,
+ PendingIdleConfirmation, PendingWorkingConfirmation, PostTaintWorkingLease,
+ PtyCausalityTracker, PtyWorkingPublishInput, ScreenDetectionPublishInput,
AGENT_PENDING_IDLE_RECHECK, AGENT_STARTUP_GRACE_WINDOW,
};
use self::terminal::{GhosttyPaneTerminal, PaneTerminal};
@@ -619,6 +620,50 @@ fn spawn_basic_detection_task(
}) {
DetectionScreenReadDecision::Read => {}
DetectionScreenReadDecision::Skip => continue,
+ DetectionScreenReadDecision::EvaluatePtyWorking => {
+ match decide_pty_working_publish_without_screen(
+ PtyWorkingPublishInput {
+ agent,
+ current_state: state,
+ last_visible_blocker,
+ last_visible_working,
+ last_visible_signal_refresh,
+ pty_activity,
+ now,
+ },
+ &mut pending_idle,
+ &mut pending_working,
+ &mut post_taint_working,
+ ) {
+ DetectionPublishDecision::NoPublish => {}
+ DetectionPublishDecision::Publish {
+ state: new_state,
+ visible_blocker,
+ visible_working,
+ process_exited: publish_process_exited,
+ } => {
+ apply_agent_detection_publish_update(
+ state_events.clone(),
+ pane_id,
+ agent,
+ AgentDetectionPublishUpdate {
+ state: new_state,
+ visible_blocker,
+ visible_working,
+ process_exited: publish_process_exited,
+ },
+ now,
+ &mut state,
+ &mut last_visible_blocker,
+ &mut last_visible_working,
+ &mut last_visible_signal_refresh,
+ &mut foreground_shell_exit_reported,
+ )
+ .await;
+ }
+ }
+ continue;
+ }
DetectionScreenReadDecision::Publish {
state: new_state,
visible_blocker,
@@ -2058,6 +2103,50 @@ impl PaneRuntime {
}) {
DetectionScreenReadDecision::Read => {}
DetectionScreenReadDecision::Skip => continue,
+ DetectionScreenReadDecision::EvaluatePtyWorking => {
+ match decide_pty_working_publish_without_screen(
+ PtyWorkingPublishInput {
+ agent,
+ current_state: state,
+ last_visible_blocker,
+ last_visible_working,
+ last_visible_signal_refresh,
+ pty_activity,
+ now,
+ },
+ &mut pending_idle,
+ &mut pending_working,
+ &mut post_taint_working,
+ ) {
+ DetectionPublishDecision::NoPublish => {}
+ DetectionPublishDecision::Publish {
+ state: new_state,
+ visible_blocker,
+ visible_working,
+ process_exited: publish_process_exited,
+ } => {
+ apply_agent_detection_publish_update(
+ state_events.clone(),
+ pane_id,
+ agent,
+ AgentDetectionPublishUpdate {
+ state: new_state,
+ visible_blocker,
+ visible_working,
+ process_exited: publish_process_exited,
+ },
+ now,
+ &mut state,
+ &mut last_visible_blocker,
+ &mut last_visible_working,
+ &mut last_visible_signal_refresh,
+ &mut foreground_shell_exit_reported,
+ )
+ .await;
+ }
+ }
+ continue;
+ }
DetectionScreenReadDecision::Publish {
state: new_state,
visible_blocker,
diff --git a/src/pane/agent_detection.rs b/src/pane/agent_detection.rs
index 1a3e795a..53e2ea31 100644
--- a/src/pane/agent_detection.rs
+++ b/src/pane/agent_detection.rs
@@ -297,6 +297,7 @@ pub(super) fn should_skip_idle_screen_scan(input: IdleScreenScanSkipInput) -> bo
pub(super) enum DetectionScreenReadDecision {
Read,
Skip,
+ EvaluatePtyWorking,
Publish {
state: AgentState,
visible_blocker: bool,
@@ -318,13 +319,16 @@ pub(super) struct DetectionScreenReadInput {
pub(super) last_screen_scan_pty_output_seq: Option<u64>,
}
+fn agent_activity_veto_requires_screen(agent: Option<Agent>) -> bool {
+ matches!(agent, Some(Agent::Claude))
+}
+
pub(super) fn decide_detection_screen_read(
input: DetectionScreenReadInput,
) -> DetectionScreenReadDecision {
if !input.agent_changed
&& !input.process_exited
&& !input.pending_idle_active
- && !input.pending_working_active
&& !input.post_taint_working_active
&& input.agent.is_some()
&& input
@@ -339,7 +343,15 @@ pub(super) fn decide_detection_screen_read(
visible_working: false,
process_exited: false,
},
- AgentState::Idle | AgentState::Unknown => DetectionScreenReadDecision::Read,
+ AgentState::Idle | AgentState::Unknown
+ if agent_activity_veto_requires_screen(input.agent)
+ && !input.pending_working_active =>
+ {
+ DetectionScreenReadDecision::Read
+ }
+ AgentState::Idle | AgentState::Unknown => {
+ DetectionScreenReadDecision::EvaluatePtyWorking
+ }
};
}
@@ -527,6 +539,74 @@ pub(super) struct ScreenDetectionPublishInput<'a> {
pub(super) now: std::time::Instant,
}
+#[derive(Debug, Clone, Copy)]
+pub(super) struct PtyWorkingPublishInput {
+ pub(super) agent: Option<Agent>,
+ pub(super) current_state: AgentState,
+ pub(super) last_visible_blocker: bool,
+ pub(super) last_visible_working: bool,
+ pub(super) last_visible_signal_refresh: Option<std::time::Instant>,
+ pub(super) pty_activity: Option<PtyActivitySignal>,
+ pub(super) now: std::time::Instant,
+}
+
+pub(super) fn decide_pty_working_publish_without_screen(
+ input: PtyWorkingPublishInput,
+ pending_idle: &mut PendingIdleConfirmation,
+ pending_working: &mut PendingWorkingConfirmation,
+ post_taint_working: &mut PostTaintWorkingLease,
+) -> DetectionPublishDecision {
+ let previous_publish = DetectionPublishState {
+ state: input.current_state,
+ visible_blocker: input.last_visible_blocker,
+ visible_working: input.last_visible_working,
+ };
+ let next_publish = DetectionPublishState {
+ state: AgentState::Working,
+ visible_blocker: false,
+ visible_working: false,
+ };
+ let stable_refresh_due = stable_visible_signal_refresh_due(
+ previous_publish,
+ next_publish,
+ input.last_visible_signal_refresh,
+ input.now,
+ );
+
+ match decide_detection_transition(
+ DetectionTransitionInput {
+ agent: input.agent,
+ previous_publish,
+ next_publish,
+ agent_changed: false,
+ process_exited: false,
+ pty_activity: input.pty_activity,
+ stable_refresh_due,
+ content: "",
+ now: input.now,
+ },
+ pending_idle,
+ pending_working,
+ post_taint_working,
+ ) {
+ DetectionTransitionDecision::NoPublish => DetectionPublishDecision::NoPublish,
+ DetectionTransitionDecision::PublishHeldWorkingBeforeExit => {
+ DetectionPublishDecision::Publish {
+ state: AgentState::Working,
+ visible_blocker: false,
+ visible_working: false,
+ process_exited: false,
+ }
+ }
+ DetectionTransitionDecision::PublishNext => DetectionPublishDecision::Publish {
+ state: AgentState::Working,
+ visible_blocker: false,
+ visible_working: false,
+ process_exited: false,
+ },
+ }
+}
+
pub(super) fn decide_screen_detection_publish(
input: ScreenDetectionPublishInput<'_>,
pending_idle: &mut PendingIdleConfirmation,
@@ -844,9 +924,17 @@ mod tests {
state: AgentState,
pty_activity: PtyActivitySignal,
) -> DetectionScreenReadInput {
+ screen_read_input_for_agent(Some(Agent::Codex), state, pty_activity)
+ }
+
+ fn screen_read_input_for_agent(
+ agent: Option<Agent>,
+ state: AgentState,
+ pty_activity: PtyActivitySignal,
+ ) -> DetectionScreenReadInput {
DetectionScreenReadInput {
state,
- agent: Some(Agent::Codex),
+ agent,
pending_idle_active: false,
pending_working_active: false,
post_taint_working_active: false,
@@ -879,7 +967,7 @@ mod tests {
AgentState::Idle,
pty_activity(true, true, 10),
)),
- DetectionScreenReadDecision::Read
+ DetectionScreenReadDecision::EvaluatePtyWorking
);
assert_eq!(
decide_detection_screen_read(screen_read_input(
@@ -918,6 +1006,15 @@ mod tests {
AgentState::Idle,
pty_activity(true, true, 11),
)),
+ DetectionScreenReadDecision::EvaluatePtyWorking
+ );
+
+ assert_eq!(
+ decide_detection_screen_read(screen_read_input_for_agent(
+ Some(Agent::Claude),
+ AgentState::Idle,
+ pty_activity(true, true, 11),
+ )),
DetectionScreenReadDecision::Read
);
}
@@ -947,6 +1044,27 @@ mod tests {
}
#[test]
+ fn screen_read_decision_keeps_active_pty_pending_working_screenless() {
+ let mut input = screen_read_input(AgentState::Idle, pty_activity(true, true, 11));
+ input.pending_working_active = true;
+ assert_eq!(
+ decide_detection_screen_read(input),
+ DetectionScreenReadDecision::EvaluatePtyWorking
+ );
+
+ let mut input = screen_read_input_for_agent(
+ Some(Agent::Claude),
+ AgentState::Idle,
+ pty_activity(true, true, 11),
+ );
+ input.pending_working_active = true;
+ assert_eq!(
+ decide_detection_screen_read(input),
+ DetectionScreenReadDecision::EvaluatePtyWorking
+ );
+ }
+
+ #[test]
fn codex_transcript_viewer_suppresses_prompt_idle_publish() {
let content = "/ T R A N S C R I P T /\n\n› yeah go ahead\n────────────────────────────────────────────────────────────────────────────────── 100% ─\n ↑/↓ to scroll pgup/pgdn to page home/end to jump\n q to quit esc to edit prev";