fix: prevent false positives from stale approval buttons in chat history

State machine no longer transitions directly from IDLE to WAITING_FOR_USER
on approval signals. Must see AGENT_WORKING first — this prevents stale
buttons like "Run this time only" persisting in chat history from
triggering the flash when no agent task is active.

Also removed "Continue" and "Resume" from approval keywords (too generic,
appear in normal chat text).

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-10 03:17:34 -04:00
parent ba656291ab
commit c0477d2f40
3 changed files with 23 additions and 11 deletions

View File

@@ -30,7 +30,7 @@ CURSOR_BUNDLE_ID = "com.todesktop.230313mzl4w4u92"
AGENT_WORKING_EXACT = {"Stop", "Cancel generating"} AGENT_WORKING_EXACT = {"Stop", "Cancel generating"}
AGENT_WORKING_PATTERNS = [re.compile(r"^Generating\b", re.IGNORECASE)] 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 = [ APPROVAL_PATTERNS = [
re.compile(r"^Run\b", re.IGNORECASE), re.compile(r"^Run\b", re.IGNORECASE),
re.compile(r"^Allow\b", re.IGNORECASE), re.compile(r"^Allow\b", re.IGNORECASE),

View File

@@ -15,20 +15,25 @@ class StateMachine:
self._last_dismiss_time: float = 0 self._last_dismiss_time: float = 0
def update(self, *, agent_working: bool, approval_needed: bool) -> bool: def update(self, *, agent_working: bool, approval_needed: bool) -> bool:
"""Update state based on detected signals. Returns True if state changed.""" """Update state based on detected signals. Returns True if state changed.
old = self.state
if approval_needed and self.state != FlasherState.WAITING_FOR_USER: Only transitions to WAITING_FOR_USER after seeing AGENT_WORKING first.
if not self._in_cooldown(): This prevents stale approval buttons in chat history from triggering
self.state = FlasherState.WAITING_FOR_USER false positives.
return self.state != old """
old = self.state
match self.state: match self.state:
case FlasherState.IDLE: case FlasherState.IDLE:
if agent_working: if agent_working:
self.state = FlasherState.AGENT_WORKING self.state = FlasherState.AGENT_WORKING
case 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(): if not self._in_cooldown():
self.state = FlasherState.WAITING_FOR_USER self.state = FlasherState.WAITING_FOR_USER
else: else:

View File

@@ -66,10 +66,17 @@ class TestStateMachine:
changed = sm.update(agent_working=False, approval_needed=False) changed = sm.update(agent_working=False, approval_needed=False)
assert sm.cooldown == 5.0 assert sm.cooldown == 5.0
def test_direct_approval_from_idle(self): def test_stale_approval_from_idle_ignored(self):
"""If we detect approval buttons without seeing agent_working first, """Approval buttons in IDLE state (stale chat history) must not trigger flash."""
still transition to WAITING_FOR_USER."""
sm = StateMachine() sm = StateMachine()
changed = sm.update(agent_working=False, approval_needed=True) 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 sm.state == FlasherState.WAITING_FOR_USER
assert changed is True assert changed is True