From cf259f9746f3fb15a934007087d412c4f988d3b4 Mon Sep 17 00:00:00 2001 From: jenkins Date: Wed, 12 Aug 2026 02:31:34 -0300 Subject: [PATCH] hermes: distinguish refreshable Claude auth --- .../auto-router/dashboard/dist/index.js | 7 +++++- .../plugins/auto-router/provider_status.py | 16 +++++++++++-- testing/tests/test_hermes_auto_router.py | 24 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/services/hermes/plugins/auto-router/dashboard/dist/index.js b/services/hermes/plugins/auto-router/dashboard/dist/index.js index 794b5f2a6..1af4c9727 100644 --- a/services/hermes/plugins/auto-router/dashboard/dist/index.js +++ b/services/hermes/plugins/auto-router/dashboard/dist/index.js @@ -29,6 +29,11 @@ function ProviderCard(props) { const item = props.item || {}; const account = item.account || null; + const authLabel = account && account.access_token_live === false && account.refreshable + ? "Authentication refreshable" + : account && account.authenticated + ? "Authentication ready" + : "Authentication unavailable"; return h(Card, { className: "provider-status-card" }, h(CardContent, { className: "provider-status-card-content" }, h("div", { className: "provider-status-card-heading" }, @@ -39,7 +44,7 @@ h(Badge, { className: "provider-status-badge provider-status-badge--" + (item.state || "unobserved") }, item.state || "unobserved") ), account ? h("div", { className: "provider-status-auth" }, - h("span", { className: account.authenticated ? "is-good" : "is-bad" }, account.authenticated ? "Authentication ready" : "Authentication unavailable"), + h("span", { className: account.authenticated ? "is-good" : "is-bad" }, authLabel), account.rate_limit_tier && account.rate_limit_tier !== "unknown" ? h("span", null, "Tier: " + account.rate_limit_tier) : null, h("span", null, "Access token: " + when(account.token_expires_at)), account.usage_url ? h("a", { diff --git a/services/hermes/plugins/auto-router/provider_status.py b/services/hermes/plugins/auto-router/provider_status.py index 71fc5ad9f..ee9a48aea 100644 --- a/services/hermes/plugins/auto-router/provider_status.py +++ b/services/hermes/plugins/auto-router/provider_status.py @@ -125,9 +125,14 @@ def _claude_account() -> dict[str, Any]: oauth = oauth if isinstance(oauth, dict) else {} expires_at, token_live = _timestamp(oauth.get("expiresAt")) refresh_expires_at, refresh_live = _timestamp(oauth.get("refreshTokenExpiresAt")) - authenticated = bool(oauth.get("accessToken")) and token_live is not False + refreshable = bool(oauth.get("refreshToken")) and refresh_live is not False + authenticated = ( + bool(oauth.get("accessToken")) and token_live is not False + ) or refreshable return { "authenticated": authenticated, + "access_token_live": token_live, + "refreshable": refreshable, "plan": oauth.get("subscriptionType") or "unknown", "rate_limit_tier": oauth.get("rateLimitTier") or "unknown", "token_expires_at": expires_at, @@ -262,7 +267,14 @@ def provider_status_text() -> str: plan = f" · plan {account.get('plan', 'unknown')}" if account else "" auth = "" if account: - auth = " · auth ready" if account.get("authenticated") else " · auth unavailable" + if account.get("access_token_live") is False and account.get("refreshable"): + auth = " · auth refreshable" + else: + auth = ( + " · auth ready" + if account.get("authenticated") + else " · auth unavailable" + ) lines.append( f"{labels[key]}: {item['state']} · {item['calls']} completed · " f"{item['errors']} errors · {item['total_tokens']} tokens{plan}{auth}" diff --git a/testing/tests/test_hermes_auto_router.py b/testing/tests/test_hermes_auto_router.py index 549257cfd..69ad268b4 100644 --- a/testing/tests/test_hermes_auto_router.py +++ b/testing/tests/test_hermes_auto_router.py @@ -217,6 +217,30 @@ def test_provider_status_accepts_iso_and_epoch_credential_expiry(): assert epoch_live is True +def test_claude_account_treats_a_live_refresh_token_as_refreshable( + tmp_path, monkeypatch +): + status = sys.modules["hermes_auto_router"].provider_status_text.__globals__ + module = sys.modules[status["provider_status_payload"].__module__] + path = tmp_path / ".credentials.json" + path.write_text(json.dumps({ + "claudeAiOauth": { + "accessToken": "expired-access", + "refreshToken": "live-refresh", + "expiresAt": 1, + "refreshTokenExpiresAt": 32_472_192_000, + "subscriptionType": "max", + } + }), encoding="utf-8") + monkeypatch.setattr(module, "CLAUDE_AUTH_PATH", path) + + account = module._claude_account() + + assert account["authenticated"] is True + assert account["access_token_live"] is False + assert account["refreshable"] is True + + def test_agent_mounts_provider_status_dashboard_into_auto_router_plugin(): import yaml