From f15e6aeb5e5bf571afcd91ab8f9ff7161b122757 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 12 Feb 2026 18:42:11 +0200 Subject: [PATCH] Add HIL to update notion page tool --- .../new_chat/tools/notion/update_page.py | 111 ++++++++++++++---- .../app/agents/new_chat/tools/registry.py | 3 +- 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py index 2e4a10d4b..6a122bd50 100644 --- a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py +++ b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py @@ -1,14 +1,17 @@ from typing import Any from langchain_core.tools import tool +from langgraph.types import interrupt from sqlalchemy.ext.asyncio import AsyncSession from app.connectors.notion_history import NotionHistoryConnector +from app.services.notion import NotionToolMetadataService def create_update_notion_page_tool( db_session: AsyncSession | None = None, search_space_id: int | None = None, + user_id: str | None = None, connector_id: int | None = None, ): """ @@ -17,6 +20,7 @@ def create_update_notion_page_tool( Args: db_session: Database session for accessing Notion connector search_space_id: Search space ID to find the Notion connector + user_id: User ID for fetching user-specific context connector_id: Optional specific connector ID (if known) Returns: @@ -42,18 +46,22 @@ def create_update_notion_page_tool( Returns: Dictionary with: - - status: "success" or "error" - - page_id: Updated page ID - - url: URL to the updated page - - title: Current page title - - message: Success or error message + - status: "success", "rejected", or "error" + - page_id: Updated page ID (if success) + - url: URL to the updated page (if success) + - title: Current page title (if success) + - message: Result message + + IMPORTANT: If status is "rejected", the user explicitly declined the action. + Respond with a brief acknowledgment (e.g., "Understood, I didn't update the page.") + and move on. Do NOT ask for alternatives or troubleshoot. Examples: - "Update the Notion page abc123 with title 'Updated Meeting Notes'" - "Change the content of page xyz789 to 'New content here'" - "Update page abc123 with new title 'Final Report' and content '# Summary...'" """ - if db_session is None or search_space_id is None: + if db_session is None or search_space_id is None or user_id is None: return { "status": "error", "message": "Notion tool not properly configured. Please contact support.", @@ -66,16 +74,71 @@ def create_update_notion_page_tool( } try: - # Get connector ID if not provided - actual_connector_id = connector_id - if actual_connector_id is None: - from sqlalchemy.future import select + metadata_service = NotionToolMetadataService(db_session) + context = await metadata_service.get_update_context( + search_space_id, user_id, page_id + ) - from app.db import SearchSourceConnector, SearchSourceConnectorType + if "error" in context: + return { + "status": "error", + "message": context["error"], + } + approval = interrupt({ + "type": "notion_page_update", + "action": { + "tool": "update_notion_page", + "params": { + "page_id": page_id, + "title": title, + "content": content, + }, + }, + "context": context, + }) + + decisions = approval.get("decisions", []) + if not decisions: + return { + "status": "error", + "message": "No approval decision received", + } + + decision = decisions[0] + decision_type = decision.get("type") or decision.get("decision_type") + + if decision_type == "reject": + return { + "status": "rejected", + "message": "User declined. The page was not updated. Do not ask again or suggest alternatives.", + } + + edited_action = decision.get("edited_action", {}) + final_params = edited_action.get("args", {}) if edited_action else {} + + final_page_id = final_params.get("page_id", page_id) + final_title = final_params.get("title", title) + final_content = final_params.get("content", content) + + if final_title and (not final_title or not final_title.strip()): + return { + "status": "error", + "message": "Page title cannot be empty. Please provide a valid title.", + } + + from sqlalchemy.future import select + + from app.db import SearchSourceConnector, SearchSourceConnectorType + + connector_id_from_context = context.get("account", {}).get("id") + + if connector_id_from_context: result = await db_session.execute( select(SearchSourceConnector).filter( + SearchSourceConnector.id == connector_id_from_context, SearchSourceConnector.search_space_id == search_space_id, + SearchSourceConnector.user_id == user_id, SearchSourceConnector.connector_type == SearchSourceConnectorType.NOTION_CONNECTOR, ) @@ -85,32 +148,36 @@ def create_update_notion_page_tool( if not connector: return { "status": "error", - "message": "No Notion connector found. Please connect Notion in your workspace settings.", + "message": "Selected Notion account is invalid or has been disconnected. Please select a valid account.", } - actual_connector_id = connector.id + else: + return { + "status": "error", + "message": "No connector found for this page.", + } - # Create connector instance notion_connector = NotionHistoryConnector( session=db_session, connector_id=actual_connector_id, ) - # Update the page result = await notion_connector.update_page( - page_id=page_id, title=title, content=content + page_id=final_page_id, + title=final_title, + content=final_content, ) return result - except ValueError as e: - return { - "status": "error", - "message": str(e), - } except Exception as e: + from langgraph.errors import GraphInterrupt + + if isinstance(e, GraphInterrupt): + raise + return { "status": "error", - "message": f"Unexpected error updating Notion page: {e!s}", + "message": str(e) if isinstance(e, ValueError) else f"Unexpected error: {e!s}", } return update_notion_page diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index 47f3d4e12..5384cdce0 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -223,8 +223,9 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ factory=lambda deps: create_update_notion_page_tool( db_session=deps["db_session"], search_space_id=deps["search_space_id"], + user_id=deps["user_id"], ), - requires=["db_session", "search_space_id"], + requires=["db_session", "search_space_id", "user_id"], ), ToolDefinition( name="delete_notion_page",