aboutsummaryrefslogtreecommitdiff
path: root/src/pane.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-10 21:00:56 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-10 21:00:56 +0300
commit0efd8eaad1620dc4b9e44c45ee16ef0a9807a200 (patch)
tree66fe2ea815e0cb3f9832a95cc7bfd18cacc194ed /src/pane.rs
parent9e0dd9412f7b2b8b2dbabf5ddcd523b4d868aed2 (diff)
fix: remove pty taint from agent detection
Diffstat (limited to 'src/pane.rs')
-rw-r--r--src/pane.rs216
1 files changed, 42 insertions, 174 deletions
diff --git a/src/pane.rs b/src/pane.rs
index b0f3bbfa..a5709ebb 100644
--- a/src/pane.rs
+++ b/src/pane.rs
@@ -32,12 +32,11 @@ mod terminal;
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_with_osc,
- handle_skipped_detection_update, observe_pty_output_activity, DetectionPublishDecision,
- DetectionScreenReadDecision, DetectionScreenReadInput, PendingIdleConfirmation,
- PendingWorkingConfirmation, PostTaintWorkingLease, PtyCausalityTracker,
- ScreenDetectionPublishInput, AGENT_PENDING_IDLE_RECHECK, AGENT_STARTUP_GRACE_WINDOW,
+ decide_detection_screen_read, decide_screen_detection_publish,
+ detection_update_for_publish_with_osc, mark_detection_content_changed,
+ observe_detection_content_change, DetectionPublishDecision, DetectionScreenReadDecision,
+ DetectionScreenReadInput, PendingIdleConfirmation, ScreenDetectionPublishInput,
+ AGENT_PENDING_IDLE_RECHECK, AGENT_STARTUP_GRACE_WINDOW,
};
use self::terminal::{GhosttyPaneTerminal, PaneTerminal};
pub(crate) use self::terminal::{TerminalDirtyPatch, TerminalDirtyPatchOutcome};
@@ -384,8 +383,7 @@ fn spawn_basic_detection_task(
pane_id: PaneId,
child_pid: Arc<AtomicU32>,
terminal: Arc<PaneTerminal>,
- pty_output_seq: Arc<AtomicU64>,
- input_write_seq: Arc<AtomicU64>,
+ detection_content_seq: Arc<AtomicU64>,
full_lifecycle_authority_active: Arc<AtomicBool>,
state_events: mpsc::Sender<AppEvent>,
) -> (
@@ -414,18 +412,12 @@ fn spawn_basic_detection_task(
let mut foreground_shell_exit_reported = false;
let mut release_was_active = false;
let mut last_detection_text = String::new();
- let mut last_screen_scan_pty_output_seq = None;
- let mut pty_causality = PtyCausalityTracker::default();
+ let mut last_screen_scan_detection_content_seq = None;
let mut agent_startup_grace_until = None;
let mut pending_idle = PendingIdleConfirmation::default();
- let mut pending_working = PendingWorkingConfirmation::default();
- let mut post_taint_working = PostTaintWorkingLease::default();
loop {
- let now_for_sleep = std::time::Instant::now();
- let sleep_duration = if pending_working.active() {
- pending_working.recheck_delay(now_for_sleep)
- } else if pending_idle.active() {
+ let sleep_duration = if pending_idle.active() {
AGENT_PENDING_IDLE_RECHECK
} else {
std::time::Duration::from_millis(300)
@@ -448,12 +440,9 @@ fn spawn_basic_detection_task(
foreground_shell_exit_reported = false;
release_was_active = false;
last_detection_text.clear();
- last_screen_scan_pty_output_seq = None;
- pty_causality = PtyCausalityTracker::default();
+ last_screen_scan_detection_content_seq = None;
agent_startup_grace_until = None;
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
}
}
@@ -541,19 +530,12 @@ fn spawn_basic_detection_task(
agent_changed = previous_agent != agent;
if agent_changed {
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
- last_screen_scan_pty_output_seq = None;
+ last_screen_scan_detection_content_seq = None;
// A new foreground agent must not inherit OSC
// title/progress evidence from the previous process.
terminal.clear_agent_osc_state();
if agent.is_some() {
agent_startup_grace_until = Some(now + AGENT_STARTUP_GRACE_WINDOW);
- baseline_pty_causality(
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq.load(Ordering::Relaxed),
- );
state = AgentState::Idle;
last_visible_idle = true;
last_visible_blocker = false;
@@ -583,8 +565,6 @@ fn spawn_basic_detection_task(
if full_lifecycle_authority_active.load(Ordering::Acquire) && !process_exited {
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
continue;
}
@@ -592,36 +572,20 @@ fn spawn_basic_detection_task(
if process_exited {
agent_startup_grace_until = None;
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
} else {
if now < until {
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
continue;
}
- baseline_pty_causality(
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq.load(Ordering::Relaxed),
- );
agent_startup_grace_until = None;
- last_screen_scan_pty_output_seq = None;
+ last_screen_scan_detection_content_seq = None;
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
continue;
}
}
- let pty_activity = if agent.is_some() {
- Some(agent_caused_pty_activity_active(
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq.load(Ordering::Relaxed),
- &mut pty_causality,
- now,
- ))
+ let current_detection_content_seq = if agent.is_some() {
+ Some(detection_content_seq.load(Ordering::Relaxed))
} else {
None
};
@@ -629,33 +593,21 @@ fn spawn_basic_detection_task(
state,
agent,
pending_idle_active: pending_idle.active(),
- pending_working_active: pending_working.active(),
- post_taint_working_active: post_taint_working.active(),
agent_changed,
process_exited,
- pty_activity,
- last_screen_scan_pty_output_seq,
+ current_detection_content_seq,
+ last_screen_scan_detection_content_seq,
}) {
DetectionScreenReadDecision::Read => {}
DetectionScreenReadDecision::Skip => continue,
}
let content = terminal.detection_text();
- last_screen_scan_pty_output_seq = pty_activity.map(|signal| signal.output_seq);
+ last_screen_scan_detection_content_seq = current_detection_content_seq;
let content_changed = content != last_detection_text;
last_detection_text.clone_from(&content);
if !process_exited && crate::detect::should_skip_state_update(agent, &content) {
- handle_skipped_detection_update(
- state,
- pty_activity,
- &mut post_taint_working,
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq.load(Ordering::Relaxed),
- now,
- );
pending_idle.clear();
- pending_working.clear();
continue;
}
sync_content_change_acquisition(
@@ -677,17 +629,7 @@ fn spawn_basic_detection_task(
&osc_progress,
process_exited,
) else {
- handle_skipped_detection_update(
- state,
- pty_activity,
- &mut post_taint_working,
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq.load(Ordering::Relaxed),
- now,
- );
pending_idle.clear();
- pending_working.clear();
continue;
};
match decide_screen_detection_publish(
@@ -700,12 +642,9 @@ fn spawn_basic_detection_task(
last_visible_signal_refresh,
process_exited,
agent_changed,
- pty_activity,
now,
},
&mut pending_idle,
- &mut pending_working,
- &mut post_taint_working,
) {
DetectionPublishDecision::NoPublish => {}
DetectionPublishDecision::Publish {
@@ -807,7 +746,7 @@ pub struct PaneRuntime {
reported_cwd: Arc<Mutex<Option<std::path::PathBuf>>>,
child_wait_completed: Option<Arc<AtomicBool>>,
kitty_keyboard_flags: Arc<AtomicU16>,
- input_write_seq: Arc<AtomicU64>,
+ detection_content_seq: Arc<AtomicU64>,
full_lifecycle_authority_active: Arc<AtomicBool>,
detect_reset_notify: Arc<Notify>,
pending_release: Arc<Mutex<Option<PendingAgentRelease>>>,
@@ -1551,25 +1490,24 @@ impl PaneRuntime {
let child_pid = Arc::new(AtomicU32::new(child_pid));
let reported_cwd = Arc::new(Mutex::new(None));
let kitty_keyboard_flags = Arc::new(AtomicU16::new(keyboard_protocol_flags));
- let input_write_seq = Arc::new(AtomicU64::new(0));
- let pty_output_seq = Arc::new(AtomicU64::new(0));
+ let detection_content_seq = Arc::new(AtomicU64::new(0));
let io = {
let terminal = terminal.clone();
let response_writer = response_tx.clone();
let render_notify = render_notify.clone();
let render_dirty = render_dirty.clone();
- let pty_output_seq = pty_output_seq.clone();
+ let detection_content_seq = detection_content_seq.clone();
let child_pid = child_pid.clone();
let read_events = events.clone();
let reported_cwd = reported_cwd.clone();
let rt = tokio::runtime::Handle::current();
let delay_rt = rt.clone();
let on_read = Box::new(move |bytes: &[u8]| {
- observe_pty_output_activity(bytes, &pty_output_seq);
let shell_pid = child_pid.load(Ordering::Acquire);
let result =
terminal.process_pty_bytes(pane_id, shell_pid, bytes, &response_writer);
+ observe_detection_content_change(bytes, &detection_content_seq);
if result.request_render && !render_dirty.swap(true, Ordering::AcqRel) {
render_notify.notify_one();
}
@@ -1618,8 +1556,7 @@ impl PaneRuntime {
pane_id,
child_pid.clone(),
terminal.clone(),
- pty_output_seq,
- input_write_seq.clone(),
+ detection_content_seq.clone(),
full_lifecycle_authority_active.clone(),
events,
);
@@ -1633,7 +1570,7 @@ impl PaneRuntime {
reported_cwd,
child_wait_completed: None,
kitty_keyboard_flags,
- input_write_seq,
+ detection_content_seq,
full_lifecycle_authority_active,
detect_reset_notify,
pending_release,
@@ -1683,8 +1620,7 @@ impl PaneRuntime {
let child_pid = Arc::new(AtomicU32::new(0));
let reported_cwd = Arc::new(Mutex::new(None));
let child_wait_completed = Arc::new(AtomicBool::new(false));
- let input_write_seq = Arc::new(AtomicU64::new(0));
- let pty_output_seq = Arc::new(AtomicU64::new(0));
+ let detection_content_seq = Arc::new(AtomicU64::new(0));
let full_lifecycle_authority_active = Arc::new(AtomicBool::new(false));
{
let child_pid = child_pid.clone();
@@ -1717,16 +1653,16 @@ impl PaneRuntime {
let response_writer = response_tx.clone();
let render_notify = render_notify.clone();
let render_dirty = render_dirty.clone();
- let pty_output_seq = pty_output_seq.clone();
+ let detection_content_seq = detection_content_seq.clone();
let child_pid = child_pid.clone();
let events = events.clone();
let reported_cwd = reported_cwd.clone();
let rt = tokio::runtime::Handle::current();
let on_read = Box::new(move |bytes: &[u8]| {
- observe_pty_output_activity(bytes, &pty_output_seq);
let shell_pid = child_pid.load(Ordering::Acquire);
let result =
terminal.process_pty_bytes(pane_id, shell_pid, bytes, &response_writer);
+ observe_detection_content_change(bytes, &detection_content_seq);
if result.request_render && !render_dirty.swap(true, Ordering::AcqRel) {
render_notify.notify_one();
}
@@ -1780,8 +1716,7 @@ impl PaneRuntime {
let child_pid = child_pid.clone();
let terminal = terminal.clone();
let state_events = events.clone();
- let pty_output_seq = pty_output_seq.clone();
- let input_write_seq_for_task = input_write_seq.clone();
+ let detection_content_seq = detection_content_seq.clone();
let full_lifecycle_authority_active_for_task = full_lifecycle_authority_active.clone();
let render_notify = render_notify.clone();
let render_dirty = render_dirty.clone();
@@ -1808,12 +1743,9 @@ impl PaneRuntime {
let mut last_visible_working = false;
let mut last_visible_signal_refresh = None;
let mut last_detection_text = String::new();
- let mut last_screen_scan_pty_output_seq = None;
- let mut pty_causality = PtyCausalityTracker::default();
+ let mut last_screen_scan_detection_content_seq = None;
let mut agent_startup_grace_until = None;
let mut pending_idle = PendingIdleConfirmation::default();
- let mut pending_working = PendingWorkingConfirmation::default();
- let mut post_taint_working = PostTaintWorkingLease::default();
tokio::time::sleep(Duration::from_millis(50)).await;
@@ -1824,8 +1756,6 @@ impl PaneRuntime {
|| terminal.has_transient_default_color_override()
{
TICK_PENDING_RELEASE
- } else if pending_working.active() {
- pending_working.recheck_delay(now_for_tick)
} else if pending_idle.active() {
AGENT_PENDING_IDLE_RECHECK
} else if agent_presence.current_agent().is_none() {
@@ -1851,12 +1781,9 @@ impl PaneRuntime {
last_visible_working = false;
last_visible_signal_refresh = None;
last_detection_text.clear();
- last_screen_scan_pty_output_seq = None;
- pty_causality = PtyCausalityTracker::default();
+ last_screen_scan_detection_content_seq = None;
agent_startup_grace_until = None;
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
}
}
@@ -1951,20 +1878,13 @@ impl PaneRuntime {
agent = agent_presence.current_agent();
if agent != previous_agent {
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
- last_screen_scan_pty_output_seq = None;
+ last_screen_scan_detection_content_seq = None;
// A new foreground agent must not inherit OSC
// title/progress evidence from the previous process.
terminal.clear_agent_osc_state();
if agent.is_some() {
agent_startup_grace_until =
Some(now + AGENT_STARTUP_GRACE_WINDOW);
- baseline_pty_causality(
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq_for_task.load(Ordering::Relaxed),
- );
state = AgentState::Idle;
last_visible_idle = true;
last_visible_blocker = false;
@@ -2025,45 +1945,27 @@ impl PaneRuntime {
&& !process_exited
{
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
continue;
}
if let Some(until) = agent_startup_grace_until {
if process_exited {
agent_startup_grace_until = None;
- last_screen_scan_pty_output_seq = None;
+ last_screen_scan_detection_content_seq = None;
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
} else {
if now < until {
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
continue;
}
- baseline_pty_causality(
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq_for_task.load(Ordering::Relaxed),
- );
agent_startup_grace_until = None;
pending_idle.clear();
- pending_working.clear();
- post_taint_working.clear();
continue;
}
}
- let pty_activity = if agent.is_some() {
- Some(agent_caused_pty_activity_active(
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq_for_task.load(Ordering::Relaxed),
- &mut pty_causality,
- now,
- ))
+ let current_detection_content_seq = if agent.is_some() {
+ Some(detection_content_seq.load(Ordering::Relaxed))
} else {
None
};
@@ -2071,33 +1973,21 @@ impl PaneRuntime {
state,
agent,
pending_idle_active: pending_idle.active(),
- pending_working_active: pending_working.active(),
- post_taint_working_active: post_taint_working.active(),
agent_changed,
process_exited,
- pty_activity,
- last_screen_scan_pty_output_seq,
+ current_detection_content_seq,
+ last_screen_scan_detection_content_seq,
}) {
DetectionScreenReadDecision::Read => {}
DetectionScreenReadDecision::Skip => continue,
}
let content = terminal.detection_text();
- last_screen_scan_pty_output_seq = pty_activity.map(|signal| signal.output_seq);
+ last_screen_scan_detection_content_seq = current_detection_content_seq;
let content_changed = content != last_detection_text;
last_detection_text.clone_from(&content);
if detect::should_skip_state_update(agent, &content) {
- handle_skipped_detection_update(
- state,
- pty_activity,
- &mut post_taint_working,
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq_for_task.load(Ordering::Relaxed),
- now,
- );
pending_idle.clear();
- pending_working.clear();
continue;
}
sync_content_change_acquisition(
@@ -2119,17 +2009,7 @@ impl PaneRuntime {
&osc_progress,
process_exited,
) else {
- handle_skipped_detection_update(
- state,
- pty_activity,
- &mut post_taint_working,
- &mut pty_causality,
- pty_output_seq.load(Ordering::Relaxed),
- input_write_seq_for_task.load(Ordering::Relaxed),
- now,
- );
pending_idle.clear();
- pending_working.clear();
continue;
};
match decide_screen_detection_publish(
@@ -2142,12 +2022,9 @@ impl PaneRuntime {
last_visible_signal_refresh,
process_exited,
agent_changed,
- pty_activity,
now,
},
&mut pending_idle,
- &mut pending_working,
- &mut post_taint_working,
) {
DetectionPublishDecision::NoPublish => {}
DetectionPublishDecision::Publish {
@@ -2193,7 +2070,7 @@ impl PaneRuntime {
reported_cwd,
child_wait_completed: Some(child_wait_completed),
kitty_keyboard_flags,
- input_write_seq,
+ detection_content_seq,
full_lifecycle_authority_active,
detect_reset_notify,
pending_release,
@@ -2242,11 +2119,11 @@ impl PaneRuntime {
if self.current_size.get() == size {
return;
}
- self.input_write_seq.fetch_add(1, Ordering::Relaxed);
self.current_size.set(size);
let terminal_responses = self
.terminal
.resize(rows, cols, cell_width_px, cell_height_px);
+ mark_detection_content_changed(&self.detection_content_seq);
self.io.resize(
rows,
cols,
@@ -2258,7 +2135,6 @@ impl PaneRuntime {
#[cfg(unix)]
pub fn nudge_child_redraw_after_handoff(&self) {
- self.input_write_seq.fetch_add(1, Ordering::Relaxed);
let (rows, cols, cell_width_px, cell_height_px) = self.current_size.get();
self.io
.nudge_child_redraw_after_handoff(rows, cols, cell_width_px, cell_height_px);
@@ -2397,19 +2273,11 @@ impl PaneRuntime {
}
pub async fn send_bytes(&self, bytes: Bytes) -> Result<(), mpsc::error::SendError<Bytes>> {
- let result = self.io.send_bytes(bytes).await;
- if result.is_ok() {
- self.input_write_seq.fetch_add(1, Ordering::Relaxed);
- }
- result
+ self.io.send_bytes(bytes).await
}
pub fn try_send_bytes(&self, bytes: Bytes) -> Result<(), mpsc::error::TrySendError<Bytes>> {
- let result = self.io.try_send_bytes(bytes);
- if result.is_ok() {
- self.input_write_seq.fetch_add(1, Ordering::Relaxed);
- }
- result
+ self.io.try_send_bytes(bytes)
}
pub async fn send_paste(&self, text: String) -> Result<(), mpsc::error::SendError<Bytes>> {
@@ -2607,7 +2475,7 @@ impl PaneRuntime {
reported_cwd: Arc::new(Mutex::new(None)),
child_wait_completed: None,
kitty_keyboard_flags: Arc::new(AtomicU16::new(0)),
- input_write_seq: Arc::new(AtomicU64::new(0)),
+ detection_content_seq: Arc::new(AtomicU64::new(0)),
full_lifecycle_authority_active: Arc::new(AtomicBool::new(false)),
detect_reset_notify: Arc::new(Notify::new()),
pending_release: Arc::new(Mutex::new(None)),
@@ -2981,7 +2849,7 @@ mod tests {
reported_cwd: Arc::new(Mutex::new(None)),
child_wait_completed: None,
kitty_keyboard_flags: Arc::new(AtomicU16::new(0)),
- input_write_seq: Arc::new(AtomicU64::new(0)),
+ detection_content_seq: Arc::new(AtomicU64::new(0)),
full_lifecycle_authority_active: Arc::new(AtomicBool::new(false)),
detect_reset_notify: Arc::new(Notify::new()),
pending_release: Arc::new(Mutex::new(None)),
@@ -3012,7 +2880,7 @@ mod tests {
reported_cwd: Arc::new(Mutex::new(None)),
child_wait_completed: None,
kitty_keyboard_flags: Arc::new(AtomicU16::new(0)),
- input_write_seq: Arc::new(AtomicU64::new(0)),
+ detection_content_seq: Arc::new(AtomicU64::new(0)),
full_lifecycle_authority_active: Arc::new(AtomicBool::new(false)),
detect_reset_notify: Arc::new(Notify::new()),
pending_release: Arc::new(Mutex::new(None)),