From 392d0170da906147137c90e2e2ddcfdee1839d68 Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Wed, 7 Oct 2026 20:24:09 +0530 Subject: [PATCH] fix(free-tier): a setup chat turn does not start the sign-in offer clock The setup chat's last turn (the fork answer and the handoff) finished clean on the free tier, so it was recorded as the first task. The offer then came due three minutes after setup ended, which lands in the middle of the first real task when that task runs longer than three minutes. Seen live on a Windows dev build: first_task_at was the setup chat's end, not the task's. Turns in the setup profile's sessions are no longer tasks, as the offer module already intended ("normally the task setup hands off to"). --- tests/tui_gateway/test_free_tier_task_done.py | 12 ++++++++++++ tui_gateway/free_tier_task_done.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/tui_gateway/test_free_tier_task_done.py b/tests/tui_gateway/test_free_tier_task_done.py index bc72435f91..62f221acec 100644 --- a/tests/tui_gateway/test_free_tier_task_done.py +++ b/tests/tui_gateway/test_free_tier_task_done.py @@ -10,6 +10,7 @@ import pytest import tui_gateway.server as server from hermes_cli import anon_auth, free_tier_offer +from hermes_cli.profiles import SETUP_PROFILE_MARKER from tui_gateway.free_tier_task_done import note_task_done CLEAN = {"completed": True, "final_response": "Here is your plan.", "user_intervened": False} @@ -56,6 +57,17 @@ def test_disqualified_turns_record_nothing(result, anonymous, display_kind, user assert not _recorded() +def test_a_setup_chat_turn_is_not_a_task(tmp_path): + """The setup chat's turns (its last one ends with the handoff) do not start the offer clock: it would + otherwise come due three minutes into the first real task.""" + setup_home = tmp_path / "home" / "profiles" / "hermes-setup" + setup_home.mkdir(parents=True) + (setup_home / SETUP_PROFILE_MARKER).write_text("{}", encoding="utf-8") + session = _session(profile_home=str(setup_home)) + note_task_done(session, dict(CLEAN), session["agent"], None) + assert not _recorded() + + @pytest.mark.parametrize("method,params", [ ("session.steer", {"text": "also check my calendar"}), ("session.redirect", {"text": "actually only Monday"}), diff --git a/tui_gateway/free_tier_task_done.py b/tui_gateway/free_tier_task_done.py index 275e258d40..c7c39b86a3 100644 --- a/tui_gateway/free_tier_task_done.py +++ b/tui_gateway/free_tier_task_done.py @@ -27,9 +27,21 @@ def is_clean_task(result: Any, agent: Any, *, user_input: bool, display_kind: st return is_anonymous_agent(agent) +def _in_setup_profile(session: dict) -> bool: + # The setup chat is not a task: counting its last turn made the offer come due during the first real task. + from pathlib import Path + + from hermes_cli.profiles import SETUP_PROFILE_MARKER + from hermes_constants import get_hermes_home + home = session.get("profile_home") + return ((Path(home) if home else get_hermes_home()) / SETUP_PROFILE_MARKER).is_file() + + def note_task_done(session: dict, result: Any, agent: Any, display_kind: str | None) -> None: """Record a clean free-tier task for the sign-in offer. Never fails the turn.""" try: + if _in_setup_profile(session): + return if is_clean_task(result, agent, user_input=bool(session.get("_turn_user_input")), display_kind=display_kind): from hermes_cli.free_tier_offer import record_task_done record_task_done()