diff --git a/src/cursor_flasher/detector.py b/src/cursor_flasher/detector.py index 6ff89cb..3d87678 100644 --- a/src/cursor_flasher/detector.py +++ b/src/cursor_flasher/detector.py @@ -30,7 +30,7 @@ CURSOR_BUNDLE_ID = "com.todesktop.230313mzl4w4u92" AGENT_WORKING_EXACT = {"Stop", "Cancel generating"} AGENT_WORKING_PATTERNS = [re.compile(r"^Generating\b", re.IGNORECASE)] -APPROVAL_EXACT = {"Accept", "Reject", "Accept All", "Deny", "Resume", "Continue"} +APPROVAL_EXACT = {"Accept", "Reject", "Accept All", "Deny"} APPROVAL_PATTERNS = [ re.compile(r"^Run\b", re.IGNORECASE), re.compile(r"^Allow\b", re.IGNORECASE), diff --git a/src/cursor_flasher/state.py b/src/cursor_flasher/state.py index b93438e..bb351b1 100644 --- a/src/cursor_flasher/state.py +++ b/src/cursor_flasher/state.py @@ -15,20 +15,25 @@ class StateMachine: self._last_dismiss_time: float = 0 def update(self, *, agent_working: bool, approval_needed: bool) -> bool: - """Update state based on detected signals. Returns True if state changed.""" - old = self.state + """Update state based on detected signals. Returns True if state changed. - if approval_needed and self.state != FlasherState.WAITING_FOR_USER: - if not self._in_cooldown(): - self.state = FlasherState.WAITING_FOR_USER - return self.state != old + Only transitions to WAITING_FOR_USER after seeing AGENT_WORKING first. + This prevents stale approval buttons in chat history from triggering + false positives. + """ + old = self.state match self.state: case FlasherState.IDLE: if agent_working: self.state = FlasherState.AGENT_WORKING case FlasherState.AGENT_WORKING: - if not agent_working: + if approval_needed and not agent_working: + if not self._in_cooldown(): + self.state = FlasherState.WAITING_FOR_USER + else: + self.state = FlasherState.IDLE + elif not agent_working: if not self._in_cooldown(): self.state = FlasherState.WAITING_FOR_USER else: diff --git a/tests/test_state.py b/tests/test_state.py index f973f44..3d38ede 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -66,10 +66,17 @@ class TestStateMachine: changed = sm.update(agent_working=False, approval_needed=False) assert sm.cooldown == 5.0 - def test_direct_approval_from_idle(self): - """If we detect approval buttons without seeing agent_working first, - still transition to WAITING_FOR_USER.""" + def test_stale_approval_from_idle_ignored(self): + """Approval buttons in IDLE state (stale chat history) must not trigger flash.""" sm = StateMachine() changed = sm.update(agent_working=False, approval_needed=True) + assert sm.state == FlasherState.IDLE + assert changed is False + + def test_approval_after_working_triggers(self): + """Approval buttons after seeing agent work should trigger flash.""" + sm = StateMachine() + sm.update(agent_working=True, approval_needed=False) + changed = sm.update(agent_working=False, approval_needed=True) assert sm.state == FlasherState.WAITING_FOR_USER assert changed is True