From b6c506abeff5157fc36f8635e7ef69a5c7209c6e Mon Sep 17 00:00:00 2001
From: CREDO23
Date: Wed, 22 Apr 2026 22:07:55 +0200
Subject: [PATCH] fix: treat all Gmail/Calendar as live connectors, hide
indexing UI
---
.../new_chat/tools/gmail/search_emails.py | 31 ++++++++++---
.../tools/google_calendar/search_events.py | 36 +---------------
.../routes/search_source_connectors_routes.py | 16 ++-----
.../app/services/composio_service.py | 2 +-
.../app/services/mcp_oauth/registry.py | 17 ++++++++
.../celery_tasks/schedule_checker_task.py | 43 ++++++++-----------
.../views/connector-edit-view.tsx | 14 +++---
.../views/indexing-configuration-view.tsx | 38 ++++++++++------
.../constants/connector-constants.ts | 36 ++++++----------
.../hooks/use-connector-dialog.ts | 8 +++-
10 files changed, 115 insertions(+), 126 deletions(-)
diff --git a/surfsense_backend/app/agents/new_chat/tools/gmail/search_emails.py b/surfsense_backend/app/agents/new_chat/tools/gmail/search_emails.py
index bfc328389..de43f03d0 100644
--- a/surfsense_backend/app/agents/new_chat/tools/gmail/search_emails.py
+++ b/surfsense_backend/app/agents/new_chat/tools/gmail/search_emails.py
@@ -15,10 +15,30 @@ _GMAIL_TYPES = [
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
]
+_token_encryption_cache: object | None = None
+
+
+def _get_token_encryption():
+ global _token_encryption_cache
+ if _token_encryption_cache is None:
+ from app.config import config
+ from app.utils.oauth_security import TokenEncryption
+
+ if not config.SECRET_KEY:
+ raise RuntimeError("SECRET_KEY not configured for token decryption.")
+ _token_encryption_cache = TokenEncryption(config.SECRET_KEY)
+ return _token_encryption_cache
+
def _build_credentials(connector: SearchSourceConnector):
- """Build Google OAuth Credentials from a Gmail connector's config."""
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR:
+ """Build Google OAuth Credentials from a connector's stored config.
+
+ Handles both native OAuth connectors (with encrypted tokens) and
+ Composio-backed connectors. Shared by Gmail and Calendar tools.
+ """
+ from app.utils.google_credentials import COMPOSIO_GOOGLE_CONNECTOR_TYPES
+
+ if connector.connector_type in COMPOSIO_GOOGLE_CONNECTOR_TYPES:
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -28,12 +48,9 @@ def _build_credentials(connector: SearchSourceConnector):
from google.oauth2.credentials import Credentials
- from app.config import config
- from app.utils.oauth_security import TokenEncryption
-
cfg = dict(connector.config)
- if cfg.get("_token_encrypted") and config.SECRET_KEY:
- enc = TokenEncryption(config.SECRET_KEY)
+ if cfg.get("_token_encrypted"):
+ enc = _get_token_encryption()
for key in ("token", "refresh_token", "client_secret"):
if cfg.get(key):
cfg[key] = enc.decrypt_token(cfg[key])
diff --git a/surfsense_backend/app/agents/new_chat/tools/google_calendar/search_events.py b/surfsense_backend/app/agents/new_chat/tools/google_calendar/search_events.py
index ad66775ef..a622b0efa 100644
--- a/surfsense_backend/app/agents/new_chat/tools/google_calendar/search_events.py
+++ b/surfsense_backend/app/agents/new_chat/tools/google_calendar/search_events.py
@@ -1,11 +1,11 @@
import logging
-from datetime import datetime
from typing import Any
from langchain_core.tools import tool
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
+from app.agents.new_chat.tools.gmail.search_emails import _build_credentials
from app.db import SearchSourceConnector, SearchSourceConnectorType
logger = logging.getLogger(__name__)
@@ -16,40 +16,6 @@ _CALENDAR_TYPES = [
]
-def _build_credentials(connector: SearchSourceConnector):
- """Build Google OAuth Credentials from a Calendar connector's config."""
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
- from app.utils.google_credentials import build_composio_credentials
-
- cca_id = connector.config.get("composio_connected_account_id")
- if not cca_id:
- raise ValueError("Composio connected account ID not found.")
- return build_composio_credentials(cca_id)
-
- from google.oauth2.credentials import Credentials
-
- from app.config import config
- from app.utils.oauth_security import TokenEncryption
-
- cfg = dict(connector.config)
- if cfg.get("_token_encrypted") and config.SECRET_KEY:
- enc = TokenEncryption(config.SECRET_KEY)
- for key in ("token", "refresh_token", "client_secret"):
- if cfg.get(key):
- cfg[key] = enc.decrypt_token(cfg[key])
-
- exp = (cfg.get("expiry") or "").replace("Z", "")
- return Credentials(
- token=cfg.get("token"),
- refresh_token=cfg.get("refresh_token"),
- token_uri=cfg.get("token_uri"),
- client_id=cfg.get("client_id"),
- client_secret=cfg.get("client_secret"),
- scopes=cfg.get("scopes", []),
- expiry=datetime.fromisoformat(exp) if exp else None,
- )
-
-
def create_search_calendar_events_tool(
db_session: AsyncSession | None = None,
search_space_id: int | None = None,
diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py
index 0c06318ee..989894003 100644
--- a/surfsense_backend/app/routes/search_source_connectors_routes.py
+++ b/surfsense_backend/app/routes/search_source_connectors_routes.py
@@ -777,19 +777,9 @@ async def index_connector_content(
# For non-calendar connectors, cap at today
indexing_to = end_date if end_date else today_str
- _LIVE_CONNECTOR_TYPES = {
- SearchSourceConnectorType.SLACK_CONNECTOR,
- SearchSourceConnectorType.TEAMS_CONNECTOR,
- SearchSourceConnectorType.LINEAR_CONNECTOR,
- SearchSourceConnectorType.JIRA_CONNECTOR,
- SearchSourceConnectorType.CLICKUP_CONNECTOR,
- SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
- SearchSourceConnectorType.AIRTABLE_CONNECTOR,
- SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
- SearchSourceConnectorType.DISCORD_CONNECTOR,
- SearchSourceConnectorType.LUMA_CONNECTOR,
- }
- if connector.connector_type in _LIVE_CONNECTOR_TYPES:
+ from app.services.mcp_oauth.registry import LIVE_CONNECTOR_TYPES
+
+ if connector.connector_type in LIVE_CONNECTOR_TYPES:
return {
"message": (
f"{connector.connector_type.value} uses real-time agent tools; "
diff --git a/surfsense_backend/app/services/composio_service.py b/surfsense_backend/app/services/composio_service.py
index 13fe37832..a8abe4aa8 100644
--- a/surfsense_backend/app/services/composio_service.py
+++ b/surfsense_backend/app/services/composio_service.py
@@ -26,7 +26,7 @@ COMPOSIO_TOOLKIT_NAMES = {
}
# Toolkits that support indexing (Phase 1: Google services only)
-INDEXABLE_TOOLKITS = {"googledrive", "gmail", "googlecalendar"}
+INDEXABLE_TOOLKITS = {"googledrive"}
# Mapping of toolkit IDs to connector types
TOOLKIT_TO_CONNECTOR_TYPE = {
diff --git a/surfsense_backend/app/services/mcp_oauth/registry.py b/surfsense_backend/app/services/mcp_oauth/registry.py
index 47a654465..49bc74d3d 100644
--- a/surfsense_backend/app/services/mcp_oauth/registry.py
+++ b/surfsense_backend/app/services/mcp_oauth/registry.py
@@ -16,6 +16,8 @@ from __future__ import annotations
from dataclasses import dataclass, field
+from app.db import SearchSourceConnectorType
+
@dataclass(frozen=True)
class MCPServiceConfig:
@@ -134,6 +136,21 @@ _CONNECTOR_TYPE_TO_SERVICE: dict[str, MCPServiceConfig] = {
svc.connector_type: svc for svc in MCP_SERVICES.values()
}
+LIVE_CONNECTOR_TYPES: frozenset[SearchSourceConnectorType] = frozenset({
+ SearchSourceConnectorType.SLACK_CONNECTOR,
+ SearchSourceConnectorType.TEAMS_CONNECTOR,
+ SearchSourceConnectorType.LINEAR_CONNECTOR,
+ SearchSourceConnectorType.JIRA_CONNECTOR,
+ SearchSourceConnectorType.CLICKUP_CONNECTOR,
+ SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR,
+ SearchSourceConnectorType.AIRTABLE_CONNECTOR,
+ SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
+ SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
+ SearchSourceConnectorType.DISCORD_CONNECTOR,
+ SearchSourceConnectorType.LUMA_CONNECTOR,
+})
+
def get_service(key: str) -> MCPServiceConfig | None:
return MCP_SERVICES.get(key)
diff --git a/surfsense_backend/app/tasks/celery_tasks/schedule_checker_task.py b/surfsense_backend/app/tasks/celery_tasks/schedule_checker_task.py
index 89010192f..373f04b48 100644
--- a/surfsense_backend/app/tasks/celery_tasks/schedule_checker_task.py
+++ b/surfsense_backend/app/tasks/celery_tasks/schedule_checker_task.py
@@ -59,9 +59,7 @@ async def _check_and_trigger_schedules():
index_crawled_urls_task,
index_elasticsearch_documents_task,
index_github_repos_task,
- index_google_calendar_events_task,
index_google_drive_files_task,
- index_google_gmail_messages_task,
index_notion_pages_task,
)
@@ -73,34 +71,29 @@ async def _check_and_trigger_schedules():
SearchSourceConnectorType.WEBCRAWLER_CONNECTOR: index_crawled_urls_task,
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR: index_google_drive_files_task,
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR: index_google_drive_files_task,
- SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR: index_google_gmail_messages_task,
- SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR: index_google_calendar_events_task,
}
- _LIVE_CONNECTOR_TYPES = {
- SearchSourceConnectorType.SLACK_CONNECTOR,
- SearchSourceConnectorType.TEAMS_CONNECTOR,
- SearchSourceConnectorType.LINEAR_CONNECTOR,
- SearchSourceConnectorType.JIRA_CONNECTOR,
- SearchSourceConnectorType.CLICKUP_CONNECTOR,
- SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
- SearchSourceConnectorType.AIRTABLE_CONNECTOR,
- SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
- SearchSourceConnectorType.DISCORD_CONNECTOR,
- SearchSourceConnectorType.LUMA_CONNECTOR,
- }
+ from app.services.mcp_oauth.registry import LIVE_CONNECTOR_TYPES
+
+ # Disable obsolete periodic indexing for live connectors in one batch.
+ live_disabled = []
+ for connector in due_connectors:
+ if connector.connector_type in LIVE_CONNECTOR_TYPES:
+ connector.periodic_indexing_enabled = False
+ connector.next_scheduled_at = None
+ live_disabled.append(connector)
+ if live_disabled:
+ await session.commit()
+ for c in live_disabled:
+ logger.info(
+ "Disabled obsolete periodic indexing for live connector %s (%s)",
+ c.id,
+ c.connector_type.value,
+ )
# Trigger indexing for each due connector
for connector in due_connectors:
- if connector.connector_type in _LIVE_CONNECTOR_TYPES:
- connector.periodic_indexing_enabled = False
- connector.next_scheduled_at = None
- await session.commit()
- logger.info(
- "Disabled obsolete periodic indexing for live connector %s (%s)",
- connector.id,
- connector.connector_type.value,
- )
+ if connector in live_disabled:
continue
# Primary guard: Redis lock indicates a task is currently running.
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx
index aa3c8d193..a69cf968f 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx
+++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx
@@ -236,8 +236,8 @@ export const ConnectorEditView: FC = ({
- {/* Quick Index Button - hidden when auth is expired */}
- {connector.is_indexable && onQuickIndex && !isAuthExpired && (
+ {/* Quick Index Button - hidden for live connectors and when auth is expired */}
+ {connector.is_indexable && !isLive && onQuickIndex && !isAuthExpired && (