diff --git a/backend/druks/user_settings/schemas.py b/backend/druks/user_settings/schemas.py index 94811650..3dbd829b 100644 --- a/backend/druks/user_settings/schemas.py +++ b/backend/druks/user_settings/schemas.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from typing import TYPE_CHECKING, Any, Literal from pydantic import BaseModel, ConfigDict, Field @@ -47,6 +47,9 @@ def from_row( connection: "HarnessConnection | None", account: "Account", ) -> "HarnessResponse": + connected = bool(connection) and ( + not connection.expires_at or connection.expires_at > datetime.now(UTC) + ) return cls( name=settings.name, provider=settings.provider, @@ -55,7 +58,7 @@ def from_row( timeout=settings.timeout, fast_mode=settings.fast_mode, allowed_models=settings.allowed_models, - connected=bool(connection), + connected=connected, kind=connection.kind if connection else None, account=account.username if connection else None, provider_email=connection.provider_email if connection else None, diff --git a/backend/tests/test_api_settings.py b/backend/tests/test_api_settings.py index ba370bfe..db767490 100644 --- a/backend/tests/test_api_settings.py +++ b/backend/tests/test_api_settings.py @@ -78,6 +78,24 @@ def test_harness_card_reports_identity(tmp_path: Path, druks_db): assert claude["providerEmail"] == "seat@corp.com" +def test_harness_card_reads_expired_token_as_not_connected(tmp_path: Path, druks_db): + from datetime import UTC, datetime, timedelta + + from druks.accounts.models import Account + from druks.harnesses.models import HarnessConnection + + HarnessConnection.connect( + harness="claude", + account=Account.get_or_create("op@example.com"), + payload={"claudeAiOauth": {"accessToken": "x"}}, + expires_at=datetime.now(UTC) - timedelta(hours=1), + provider_email="seat@corp.com", + ) + with _build_client(tmp_path) as client: + claude = {h["name"]: h for h in client.get("/api/settings/harnesses").json()}["claude"] + assert claude["connected"] is False + + def test_disconnect_removes_only_the_requesting_accounts_connection(tmp_path: Path, druks_db): from conftest import connect_harness from druks.harnesses.claude import ClaudeHarness diff --git a/frontend/src/components/BrowserSessionsPane.tsx b/frontend/src/components/BrowserSessionsPane.tsx index 8e244c39..91047e07 100644 --- a/frontend/src/components/BrowserSessionsPane.tsx +++ b/frontend/src/components/BrowserSessionsPane.tsx @@ -56,7 +56,7 @@ export function BrowserSessionsPane() { return (
-

Browser sessions

+

Browser

Sign-ins your extensions declare, kept as encrypted browser state.

diff --git a/frontend/src/components/SettingsModal.test.tsx b/frontend/src/components/SettingsModal.test.tsx index 39aa517f..8052f069 100644 --- a/frontend/src/components/SettingsModal.test.tsx +++ b/frontend/src/components/SettingsModal.test.tsx @@ -185,9 +185,9 @@ describe('SettingsModal extension fields', () => { stubFetch() renderModal() - fireEvent.click(await screen.findByRole('button', { name: 'Browser sessions' })) + fireEvent.click(await screen.findByRole('button', { name: 'Browser' })) - expect(await screen.findByRole('heading', { name: 'Browser sessions' })).toBeTruthy() + expect(await screen.findByRole('heading', { name: 'Browser' })).toBeTruthy() expect( await screen.findByText('No installed extension declares a browser session.'), ).toBeTruthy() diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 21ad4107..cf70e698 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -350,7 +350,7 @@ export function SettingsModal({ open, onClose }: Props) { setSection('general')} /> setSection('harnesses')} /> setSection('services')} /> - setSection('browser-sessions')} /> + setSection('browser-sessions')} /> setSection('skills')} /> setSection('mcp')} /> setSection('agent-access')} />