chore: linting

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-02-15 23:38:15 -08:00
parent b9159a8329
commit 81c70befcf
10 changed files with 265 additions and 180 deletions

View file

@ -249,7 +249,11 @@ async def create_surfsense_deep_agent(
available_connectors is not None and "NOTION_CONNECTOR" in available_connectors
)
if not has_notion_connector:
notion_tools = ["create_notion_page", "update_notion_page", "delete_notion_page"]
notion_tools = [
"create_notion_page",
"update_notion_page",
"delete_notion_page",
]
modified_disabled_tools.extend(notion_tools)
# Build tools using the async registry (includes MCP tools)

View file

@ -55,19 +55,23 @@ def create_create_notion_page_tool(
- url: URL to the created page (if success)
- title: 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 create the page.")
Respond with a brief acknowledgment (e.g., "Understood, I didn't create the page.")
and move on. Do NOT ask for parent page IDs, troubleshoot, or suggest alternatives.
Examples:
- "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'"
- "Save this to Notion with title 'Research Summary'"
"""
logger.info(f"create_notion_page called: title='{title}', parent_page_id={parent_page_id}")
logger.info(
f"create_notion_page called: title='{title}', parent_page_id={parent_page_id}"
)
if db_session is None or search_space_id is None or user_id is None:
logger.error("Notion tool not properly configured - missing required parameters")
logger.error(
"Notion tool not properly configured - missing required parameters"
)
return {
"status": "error",
"message": "Notion tool not properly configured. Please contact support.",
@ -75,30 +79,34 @@ def create_create_notion_page_tool(
try:
metadata_service = NotionToolMetadataService(db_session)
context = await metadata_service.get_creation_context(search_space_id, user_id)
context = await metadata_service.get_creation_context(
search_space_id, user_id
)
if "error" in context:
logger.error(f"Failed to fetch creation context: {context['error']}")
return {
"status": "error",
"message": context["error"],
}
logger.info(f"Requesting approval for creating Notion page: '{title}'")
approval = interrupt({
"type": "notion_page_creation",
"action": {
"tool": "create_notion_page",
"params": {
"title": title,
"content": content,
"parent_page_id": parent_page_id,
"connector_id": connector_id,
approval = interrupt(
{
"type": "notion_page_creation",
"action": {
"tool": "create_notion_page",
"params": {
"title": title,
"content": content,
"parent_page_id": parent_page_id,
"connector_id": connector_id,
},
},
},
"context": context,
})
"context": context,
}
)
decisions = approval.get("decisions", [])
if not decisions:
logger.warning("No approval decision received")
@ -106,35 +114,37 @@ def create_create_notion_page_tool(
"status": "error",
"message": "No approval decision received",
}
decision = decisions[0]
decision_type = decision.get("type") or decision.get("decision_type")
logger.info(f"User decision: {decision_type}")
if decision_type == "reject":
logger.info("Notion page creation rejected by user")
return {
"status": "rejected",
"message": "User declined. The page was not created. Do not ask again or suggest alternatives.",
}
edited_action = decision.get("edited_action", {})
final_params = edited_action.get("args", {}) if edited_action else {}
final_title = final_params.get("title", title)
final_content = final_params.get("content", content)
final_parent_page_id = final_params.get("parent_page_id", parent_page_id)
final_connector_id = final_params.get("connector_id", connector_id)
if not final_title or not final_title.strip():
logger.error("Title is empty or contains only whitespace")
return {
"status": "error",
"message": "Page title cannot be empty. Please provide a valid title.",
}
logger.info(f"Creating Notion page with final params: title='{final_title}'")
logger.info(
f"Creating Notion page with final params: title='{final_title}'"
)
from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType
@ -152,7 +162,9 @@ def create_create_notion_page_tool(
connector = result.scalars().first()
if not connector:
logger.warning(f"No Notion connector found for search_space_id={search_space_id}")
logger.warning(
f"No Notion connector found for search_space_id={search_space_id}"
)
return {
"status": "error",
"message": "No Notion connector found. Please connect Notion in your workspace settings.",
@ -192,19 +204,23 @@ def create_create_notion_page_tool(
content=final_content,
parent_page_id=final_parent_page_id,
)
logger.info(f"create_page result: {result.get('status')} - {result.get('message', '')}")
logger.info(
f"create_page result: {result.get('status')} - {result.get('message', '')}"
)
return result
except Exception as e:
from langgraph.errors import GraphInterrupt
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error creating Notion page: {e}", exc_info=True)
return {
"status": "error",
"message": str(e) if isinstance(e, ValueError) else f"Unexpected error: {e!s}",
"message": str(e)
if isinstance(e, ValueError)
else f"Unexpected error: {e!s}",
}
return create_notion_page

View file

@ -59,10 +59,14 @@ def create_delete_notion_page_tool(
- "Remove the 'Old Project Plan' Notion page"
- "Archive the 'Draft Ideas' Notion page"
"""
logger.info(f"delete_notion_page called: page_title='{page_title}', delete_from_db={delete_from_db}")
logger.info(
f"delete_notion_page called: page_title='{page_title}', delete_from_db={delete_from_db}"
)
if db_session is None or search_space_id is None or user_id is None:
logger.error("Notion tool not properly configured - missing required parameters")
logger.error(
"Notion tool not properly configured - missing required parameters"
)
return {
"status": "error",
"message": "Notion tool not properly configured. Please contact support.",
@ -95,8 +99,10 @@ def create_delete_notion_page_tool(
connector_id_from_context = context.get("account", {}).get("id")
document_id = context.get("document_id")
logger.info(f"Requesting approval for deleting Notion page: '{page_title}' (page_id={page_id}, delete_from_db={delete_from_db})")
logger.info(
f"Requesting approval for deleting Notion page: '{page_title}' (page_id={page_id}, delete_from_db={delete_from_db})"
)
# Request approval before deleting
approval = interrupt(
{
@ -137,10 +143,14 @@ def create_delete_notion_page_tool(
final_params = edited_action.get("args", {}) if edited_action else {}
final_page_id = final_params.get("page_id", page_id)
final_connector_id = final_params.get("connector_id", connector_id_from_context)
final_connector_id = final_params.get(
"connector_id", connector_id_from_context
)
final_delete_from_db = final_params.get("delete_from_db", delete_from_db)
logger.info(f"Deleting Notion page with final params: page_id={final_page_id}, connector_id={final_connector_id}, delete_from_db={final_delete_from_db}")
logger.info(
f"Deleting Notion page with final params: page_id={final_page_id}, connector_id={final_connector_id}, delete_from_db={final_delete_from_db}"
)
from sqlalchemy.future import select
@ -184,11 +194,17 @@ def create_delete_notion_page_tool(
# Delete the page from Notion
result = await notion_connector.delete_page(page_id=final_page_id)
logger.info(f"delete_page result: {result.get('status')} - {result.get('message', '')}")
logger.info(
f"delete_page result: {result.get('status')} - {result.get('message', '')}"
)
# If deletion was successful and user wants to delete from DB
deleted_from_db = False
if result.get("status") == "success" and final_delete_from_db and document_id:
if (
result.get("status") == "success"
and final_delete_from_db
and document_id
):
try:
from sqlalchemy.future import select
@ -204,21 +220,27 @@ def create_delete_notion_page_tool(
await db_session.delete(document)
await db_session.commit()
deleted_from_db = True
logger.info(f"Deleted document {document_id} from knowledge base")
logger.info(
f"Deleted document {document_id} from knowledge base"
)
else:
logger.warning(f"Document {document_id} not found in DB")
except Exception as e:
logger.error(f"Failed to delete document from DB: {e}")
# Don't fail the whole operation if DB deletion fails
# The page is already deleted from Notion, so inform the user
result["warning"] = f"Page deleted from Notion, but failed to remove from knowledge base: {e!s}"
result["warning"] = (
f"Page deleted from Notion, but failed to remove from knowledge base: {e!s}"
)
# Update result with DB deletion status
if result.get("status") == "success":
result["deleted_from_db"] = deleted_from_db
if deleted_from_db:
result["message"] = f"{result.get('message', '')} (also removed from knowledge base)"
result["message"] = (
f"{result.get('message', '')} (also removed from knowledge base)"
)
return result
except Exception as e:

View file

@ -51,24 +51,28 @@ def create_update_notion_page_tool(
- url: URL to the updated page (if success)
- title: Current page title (if success)
- message: Result message
IMPORTANT:
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.")
Respond with a brief acknowledgment (e.g., "Understood, I didn't update the page.")
and move on. Do NOT ask for alternatives or troubleshoot.
- If status is "not_found", inform the user conversationally using the exact message provided.
Example: "I couldn't find the page '[page_title]' in your indexed Notion pages. [message details]"
Do NOT treat this as an error. Do NOT invent information. Simply relay the message and
Do NOT treat this as an error. Do NOT invent information. Simply relay the message and
ask the user to verify the page title or check if it's been indexed.
Examples:
- "Add 'New meeting notes from today' to the 'Meeting Notes' Notion page"
- "Append the following to the 'Project Plan' Notion page: '# Status Update\n\nCompleted phase 1'"
"""
logger.info(f"update_notion_page called: page_title='{page_title}', content_length={len(content) if content else 0}")
logger.info(
f"update_notion_page called: page_title='{page_title}', content_length={len(content) if content else 0}"
)
if db_session is None or search_space_id is None or user_id is None:
logger.error("Notion tool not properly configured - missing required parameters")
logger.error(
"Notion tool not properly configured - missing required parameters"
)
return {
"status": "error",
"message": "Notion tool not properly configured. Please contact support.",
@ -106,7 +110,9 @@ def create_update_notion_page_tool(
page_id = context.get("page_id")
connector_id_from_context = context.get("account", {}).get("id")
logger.info(f"Requesting approval for updating Notion page: '{page_title}' (page_id={page_id})")
logger.info(
f"Requesting approval for updating Notion page: '{page_title}' (page_id={page_id})"
)
approval = interrupt(
{
"type": "notion_page_update",
@ -146,9 +152,13 @@ def create_update_notion_page_tool(
final_page_id = final_params.get("page_id", page_id)
final_content = final_params.get("content", content)
final_connector_id = final_params.get("connector_id", connector_id_from_context)
final_connector_id = final_params.get(
"connector_id", connector_id_from_context
)
logger.info(f"Updating Notion page with final params: page_id={final_page_id}, has_content={final_content is not None}")
logger.info(
f"Updating Notion page with final params: page_id={final_page_id}, has_content={final_content is not None}"
)
from sqlalchemy.future import select
@ -192,7 +202,9 @@ def create_update_notion_page_tool(
page_id=final_page_id,
content=final_content,
)
logger.info(f"update_page result: {result.get('status')} - {result.get('message', '')}")
logger.info(
f"update_page result: {result.get('status')} - {result.get('message', '')}"
)
return result
except Exception as e:

View file

@ -1,4 +1,5 @@
import asyncio
import contextlib
import logging
import re
from collections.abc import Awaitable, Callable
@ -220,6 +221,7 @@ class NotionHistoryConnector:
# Refresh token
from app.routes.notion_add_connector_route import refresh_notion_token
connector = await refresh_notion_token(self._session, connector)
# Reload credentials after refresh
@ -804,7 +806,7 @@ class NotionHistoryConnector:
results = response.get("results", [])
if results:
return results[0]["id"]
return None
except Exception as e:
@ -835,59 +837,81 @@ class NotionHistoryConnector:
# Heading 1
if line.startswith("# "):
blocks.append({
"object": "block",
"type": "heading_1",
"heading_1": {
"rich_text": [{"type": "text", "text": {"content": line[2:]}}]
},
})
blocks.append(
{
"object": "block",
"type": "heading_1",
"heading_1": {
"rich_text": [
{"type": "text", "text": {"content": line[2:]}}
]
},
}
)
# Heading 2
elif line.startswith("## "):
blocks.append({
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": line[3:]}}]
},
})
blocks.append(
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [
{"type": "text", "text": {"content": line[3:]}}
]
},
}
)
# Heading 3
elif line.startswith("### "):
blocks.append({
"object": "block",
"type": "heading_3",
"heading_3": {
"rich_text": [{"type": "text", "text": {"content": line[4:]}}]
},
})
blocks.append(
{
"object": "block",
"type": "heading_3",
"heading_3": {
"rich_text": [
{"type": "text", "text": {"content": line[4:]}}
]
},
}
)
# Bullet list
elif line.startswith("- ") or line.startswith("* "):
blocks.append({
"object": "block",
"type": "bulleted_list_item",
"bulleted_list_item": {
"rich_text": [{"type": "text", "text": {"content": line[2:]}}]
},
})
blocks.append(
{
"object": "block",
"type": "bulleted_list_item",
"bulleted_list_item": {
"rich_text": [
{"type": "text", "text": {"content": line[2:]}}
]
},
}
)
# Numbered list
elif (match := re.match(r'^(\d+)\.\s+(.*)$', line)):
elif match := re.match(r"^(\d+)\.\s+(.*)$", line):
content = match.group(2) # Extract text after "number. "
blocks.append({
"object": "block",
"type": "numbered_list_item",
"numbered_list_item": {
"rich_text": [{"type": "text", "text": {"content": content}}]
},
})
blocks.append(
{
"object": "block",
"type": "numbered_list_item",
"numbered_list_item": {
"rich_text": [
{"type": "text", "text": {"content": content}}
]
},
}
)
# Regular paragraph
else:
blocks.append({
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": line}}]
},
})
blocks.append(
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": line}}]
},
}
)
return blocks
@ -914,8 +938,10 @@ class NotionHistoryConnector:
APIResponseError: If Notion API returns an error
"""
try:
logger.info(f"Creating Notion page: title='{title}', parent_page_id={parent_page_id}")
logger.info(
f"Creating Notion page: title='{title}', parent_page_id={parent_page_id}"
)
# Get Notion client
notion = await self._get_client()
@ -924,14 +950,16 @@ class NotionHistoryConnector:
# Prepare parent - find first available page if not provided
if not parent_page_id:
logger.info("No parent_page_id provided, searching for first accessible page...")
logger.info(
"No parent_page_id provided, searching for first accessible page..."
)
parent_page_id = await self._get_first_accessible_parent()
if not parent_page_id:
logger.warning("No accessible parent pages found")
return {
"status": "error",
"message": "Could not find any accessible Notion pages to use as parent. "
"Please make sure your Notion integration has access to at least one page.",
"Please make sure your Notion integration has access to at least one page.",
}
logger.info(f"Using parent_page_id: {parent_page_id}")
@ -939,9 +967,7 @@ class NotionHistoryConnector:
# Create the page with standard title property
properties = {
"title": {
"title": [{"type": "text", "text": {"content": title}}]
}
"title": {"title": [{"type": "text", "text": {"content": title}}]}
}
response = await self._api_call_with_retry(
@ -959,9 +985,7 @@ class NotionHistoryConnector:
for i in range(100, len(children), 100):
batch = children[i : i + 100]
await self._api_call_with_retry(
notion.blocks.children.append,
block_id=page_id,
children=batch
notion.blocks.children.append, block_id=page_id, children=batch
)
return {
@ -991,7 +1015,7 @@ class NotionHistoryConnector:
) -> dict[str, Any]:
"""
Update an existing Notion page by appending new content.
Note: Content is appended to the page, not replaced.
Args:
@ -1013,7 +1037,9 @@ class NotionHistoryConnector:
try:
children = self._markdown_to_blocks(content)
if not children:
logger.warning("No blocks generated from content, skipping append")
logger.warning(
"No blocks generated from content, skipping append"
)
return {
"status": "error",
"message": "Content conversion failed: no valid blocks generated",
@ -1032,9 +1058,11 @@ class NotionHistoryConnector:
await self._api_call_with_retry(
notion.blocks.children.append,
block_id=page_id,
children=batch
children=batch,
)
logger.info(f"Successfully appended {len(children)} new blocks to page {page_id}")
logger.info(
f"Successfully appended {len(children)} new blocks to page {page_id}"
)
except Exception as e:
logger.error(f"Failed to append content blocks: {e}")
return {
@ -1044,8 +1072,7 @@ class NotionHistoryConnector:
# Get updated page info
response = await self._api_call_with_retry(
notion.pages.retrieve,
page_id=page_id
notion.pages.retrieve, page_id=page_id
)
page_url = response["url"]
page_title = response["properties"]["title"]["title"][0]["text"]["content"]
@ -1092,18 +1119,14 @@ class NotionHistoryConnector:
# Archive the page (Notion's way of "deleting")
response = await self._api_call_with_retry(
notion.pages.update,
page_id=page_id,
archived=True
notion.pages.update, page_id=page_id, archived=True
)
page_title = "Unknown"
try:
with contextlib.suppress(KeyError, IndexError):
page_title = response["properties"]["title"]["title"][0]["text"][
"content"
]
except (KeyError, IndexError):
pass
return {
"status": "success",

View file

@ -518,7 +518,9 @@ class VercelStreamingService:
normalized_payload = self._normalize_interrupt_payload(interrupt_value)
return self.format_data("interrupt-request", normalized_payload)
def _normalize_interrupt_payload(self, interrupt_value: dict[str, Any]) -> dict[str, Any]:
def _normalize_interrupt_payload(
self, interrupt_value: dict[str, Any]
) -> dict[str, Any]:
"""Normalize interrupt payloads from different sources into a consistent format.
Handles two interrupt sources:

View file

@ -33,8 +33,8 @@ import { membersAtom } from "@/atoms/members/members-query.atoms";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { Thread } from "@/components/assistant-ui/thread";
import { ChatHeader } from "@/components/new-chat/chat-header";
import { CreateNotionPageToolUI } from "@/components/tool-ui/create-notion-page";
import { ReportPanel } from "@/components/report-panel/report-panel";
import { CreateNotionPageToolUI } from "@/components/tool-ui/create-notion-page";
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
import { DeleteNotionPageToolUI } from "@/components/tool-ui/delete-notion-page";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
@ -969,36 +969,36 @@ export default function NewChatPage() {
contentPartsState.currentTextPartIndex = -1;
}
}
}
}
// Merge edited args if present to fix race condition
if (decisions.length > 0 && decisions[0].type === "edit" && decisions[0].edited_action) {
const editedAction = decisions[0].edited_action;
for (const part of contentParts) {
if (part.type === "tool-call" && part.toolName === editedAction.name) {
part.args = { ...part.args, ...editedAction.args };
break;
}
}
}
const decisionType = decisions[0]?.type as "approve" | "reject" | undefined;
if (decisionType) {
for (const part of contentParts) {
if (
part.type === "tool-call" &&
typeof part.result === "object" &&
part.result !== null &&
"__interrupt__" in (part.result as Record<string, unknown>)
) {
part.result = {
...(part.result as Record<string, unknown>),
__decided__: decisionType,
};
// Merge edited args if present to fix race condition
if (decisions.length > 0 && decisions[0].type === "edit" && decisions[0].edited_action) {
const editedAction = decisions[0].edited_action;
for (const part of contentParts) {
if (part.type === "tool-call" && part.toolName === editedAction.name) {
part.args = { ...part.args, ...editedAction.args };
break;
}
}
}
const decisionType = decisions[0]?.type as "approve" | "reject" | undefined;
if (decisionType) {
for (const part of contentParts) {
if (
part.type === "tool-call" &&
typeof part.result === "object" &&
part.result !== null &&
"__interrupt__" in (part.result as Record<string, unknown>)
) {
part.result = {
...(part.result as Record<string, unknown>),
__decided__: decisionType,
};
}
}
}
}
try {
const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";

View file

@ -10,9 +10,9 @@ import {
} from "@assistant-ui/react-markdown";
import { CheckIcon, CopyIcon } from "lucide-react";
import { type FC, memo, type ReactNode, useState } from "react";
import rehypeKatex from "rehype-katex";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import { InlineCitation } from "@/components/assistant-ui/inline-citation";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";

View file

@ -68,7 +68,12 @@ interface WarningResult {
message?: string;
}
type DeleteNotionPageResult = InterruptResult | SuccessResult | ErrorResult | InfoResult | WarningResult;
type DeleteNotionPageResult =
| InterruptResult
| SuccessResult
| ErrorResult
| InfoResult
| WarningResult;
function isInterruptResult(result: unknown): result is InterruptResult {
return (
@ -341,22 +346,23 @@ function SuccessCard({ result }: { result: SuccessResult }) {
</p>
</div>
</div>
{(result.deleted_from_db || result.title) && (
<div className="space-y-2 px-4 py-3 text-xs">
{result.title && (
<div>
<span className="font-medium text-muted-foreground">Deleted page: </span>
<span>{result.title}</span>
</div>
)}
{result.deleted_from_db && (
<div className="pt-1">
<span className="text-green-600 dark:text-green-500">
Also removed from knowledge base
</span>
</div>
)}
</div>)}
{(result.deleted_from_db || result.title) && (
<div className="space-y-2 px-4 py-3 text-xs">
{result.title && (
<div>
<span className="font-medium text-muted-foreground">Deleted page: </span>
<span>{result.title}</span>
</div>
)}
{result.deleted_from_db && (
<div className="pt-1">
<span className="text-green-600 dark:text-green-500">
Also removed from knowledge base
</span>
</div>
)}
</div>
)}
</div>
);
}

View file

@ -129,15 +129,15 @@ export function useInbox(
// Skip if already syncing with this key
if (userSyncKeyRef.current === userSyncKey) return;
// Clean up previous sync
if (syncHandleRef.current) {
try {
syncHandleRef.current.unsubscribe();
} catch {
// PGlite may already be closed during cleanup
// Clean up previous sync
if (syncHandleRef.current) {
try {
syncHandleRef.current.unsubscribe();
} catch {
// PGlite may already be closed during cleanup
}
syncHandleRef.current = null;
}
syncHandleRef.current = null;
}
console.log("[useInbox] Starting sync for:", userId);
userSyncKeyRef.current = userSyncKey;