From c60d26e631d127a5919f9351603d3e63f1fa40e2 Mon Sep 17 00:00:00 2001 From: abccodes Date: Thu, 25 Jun 2026 18:04:53 -0700 Subject: [PATCH] [aidan] fix/free-trial: clear trial on subscription connect so a connected sub takes over immediately --- backend/apps/agents/agents.py | 5 +++++ backend/apps/subscription/free_trial.py | 13 ++++++++++++- backend/main.py | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py index 5e5094a6..fc685dee 100644 --- a/backend/apps/agents/agents.py +++ b/backend/apps/agents/agents.py @@ -425,6 +425,8 @@ async def subscriptions_poll(body: dict): from backend.apps.service.client import sync as _sync from backend.apps.settings.settings import load_settings _sync(load_settings().model_dump()) + from backend.apps.subscription.free_trial import clear_free_trial_on_connect + await clear_free_trial_on_connect() return result except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -459,6 +461,9 @@ async def subscriptions_exchange(body: dict): from backend.apps.service.client import sync as do_sync from backend.apps.settings.settings import load_settings do_sync(load_settings().model_dump()) + # A connected subscription takes precedence over the free trial right away. + from backend.apps.subscription.free_trial import clear_free_trial_on_connect + await clear_free_trial_on_connect() return result except Exception as e: if state and state in completed_oauth: diff --git a/backend/apps/subscription/free_trial.py b/backend/apps/subscription/free_trial.py index 4f8786f1..24155902 100644 --- a/backend/apps/subscription/free_trial.py +++ b/backend/apps/subscription/free_trial.py @@ -22,7 +22,7 @@ import time import httpx from backend.apps.settings.credentials import OPENSWARM_DEFAULT_PROXY_URL -from backend.apps.settings.settings import save_settings_async +from backend.apps.settings.settings import load_settings, save_settings_async logger = logging.getLogger(__name__) @@ -152,6 +152,17 @@ async def clear_free_trial(settings_obj) -> None: await _sync_routing(settings_obj) +async def clear_free_trial_on_connect() -> None: + """Hand the wheel back to a just-connected subscription immediately, instead of + waiting for the next-boot arm_free_trial reconcile. Subscriptions live in 9Router, + not settings, so `apply_settings_update`'s `_has_own_model` clear (which covers keys + + custom providers) can't see them; this is the connect-time equivalent for subs.""" + try: + await clear_free_trial(load_settings()) + except Exception as e: + logger.debug("clear_free_trial_on_connect skipped: %s", e) + + async def arm_free_trial(settings_obj) -> dict: """Mint (or re-fetch) the machine's grant and, if runs remain, flip into free-trial mode. Guarded: never arms over a real key/subscription.""" diff --git a/backend/main.py b/backend/main.py index 457c47d5..6933f555 100644 --- a/backend/main.py +++ b/backend/main.py @@ -511,6 +511,12 @@ async def subscriptions_callback(request: Request): _mark_oauth_completed(state) logger.info(f"OAuth exchange succeeded for provider={pending.get('provider')}") + # A connected subscription takes precedence over the free trial right away. + try: + from backend.apps.subscription.free_trial import clear_free_trial_on_connect + await clear_free_trial_on_connect() + except Exception: + pass return HTMLResponse(_SUCCESS_HTML)