From 4ffa76712769e793738e2e2d61b59f2f34254cfd Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:35:21 -0700 Subject: [PATCH] fix(bot-screen): no-op lease transitions do not bump the epoch release() on an agent-held lease and a same-viewer re-acquire rewrote the record and bumped the epoch, so an admitted in-flight agent action looked overtaken and was voided by a double-clicked Hand back or a stray CLI stop; the re-acquire also reset `since` and wiped the on-screen reason. --- tests/tools/test_bot_desktop_lease.py | 13 +++++++++++++ tools/bot_desktop/lease.py | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/tests/tools/test_bot_desktop_lease.py b/tests/tools/test_bot_desktop_lease.py index 08cc9922fa..f85a719718 100644 --- a/tests/tools/test_bot_desktop_lease.py +++ b/tests/tools/test_bot_desktop_lease.py @@ -193,3 +193,16 @@ def test_lease_files_are_private_even_when_the_lease_is_written_before_the_scree for path in (sd, sd / "lease.json", sd / "lease.lock"): assert path.exists(), path assert stat.S_IMODE(path.stat().st_mode) & 0o077 == 0, f"{path.name} is {oct(path.stat().st_mode)}" + + +def test_no_op_transitions_do_not_bump_the_epoch(): + """Callers void an admitted in-flight action when the epoch moved. A release on an agent-held lease + (double-clicked Hand back, a stray CLI stop) or the same viewer re-acquiring changes nothing real, so + it must not make a legitimate agent action look overtaken.""" + e0 = lease.get().epoch + assert lease.release().epoch == e0 + got = lease.acquire("v1", reason="log in please") + assert got.epoch == e0 + 1 + again = lease.acquire("v1") + assert again.epoch == got.epoch and again.reason == "log in please" and again.since == got.since + assert lease.acquire("v2").epoch == got.epoch + 1 # a different viewer IS a transition diff --git a/tools/bot_desktop/lease.py b/tools/bot_desktop/lease.py index 121df974db..8941152bbe 100644 --- a/tools/bot_desktop/lease.py +++ b/tools/bot_desktop/lease.py @@ -170,6 +170,8 @@ def acquire(viewer_id: str, *, profile_key: Optional[str] = None, reason: str = """Human ``viewer_id`` takes control. Last writer wins: a second viewer evicts the first, and the RFB bridge closes the evicted socket so its UI drops to view-only.""" def _m(lease: Lease) -> bool: + if lease.holder == HUMAN and lease.viewer_id == viewer_id: + return False # already theirs: no epoch bump, `since` and the reason on screen stay put # The agent's ask ("please log in to X") stays as the takeover reason: the human needs it # on screen WHILE they act, not only before they clicked Take over. lease.holder, lease.viewer_id, lease.since = HUMAN, viewer_id, time.time() @@ -194,6 +196,10 @@ def release(viewer_id: Optional[str] = None, *, profile_key: Optional[str] = Non # caller that never inspects the return value leaves a trace. logger.info("bot-desktop lease: release by %r ignored, another viewer holds", viewer_id) return False + if lease.holder == AGENT: + # Already the agent's. Bumping the epoch here would make a legitimately admitted in-flight + # agent action (a double-clicked Hand back, a stray CLI stop) look overtaken and get voided. + return False lease.holder, lease.viewer_id, lease.since, lease.reason = AGENT, None, time.time(), "" return True return _transition(profile_key, _m)