From 083a9f794604750d5859ef2b39dcf2b242519268 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 1 May 2026 23:17:46 +0200 Subject: [PATCH] Add main-agent tool allowlist plus permission and prune helpers. --- .../main_agent/__init__.py | 5 +++ .../main_agent/context_prune/__init__.py | 7 ++++ .../context_prune/prune_tool_names.py | 26 ++++++++++++ .../main_agent/permissions/__init__.py | 13 ++++++ .../permissions/connector_deny_rules.py | 21 ++++++++++ .../permissions/connector_gated_tool_names.py | 42 +++++++++++++++++++ .../main_agent/permissions/rule.py | 15 +++++++ .../main_agent/tools/__init__.py | 7 ++++ .../main_agent/tools/index.py | 17 ++++++++ 9 files changed, 153 insertions(+) create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/prune_tool_names.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_deny_rules.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_gated_tool_names.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/rule.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/index.py diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/__init__.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/__init__.py new file mode 100644 index 000000000..733150645 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/__init__.py @@ -0,0 +1,5 @@ +"""SurfSense main-agent package (factory export added when runtime lands).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/__init__.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/__init__.py new file mode 100644 index 000000000..550ba54c5 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/__init__.py @@ -0,0 +1,7 @@ +"""Tool-name pruning for context editing (exclude lists without dropping protected tools).""" + +from __future__ import annotations + +from .prune_tool_names import PRUNE_PROTECTED_TOOL_NAMES, safe_exclude_tools + +__all__ = ["PRUNE_PROTECTED_TOOL_NAMES", "safe_exclude_tools"] diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/prune_tool_names.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/prune_tool_names.py new file mode 100644 index 000000000..c8bf6d6e0 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/context_prune/prune_tool_names.py @@ -0,0 +1,26 @@ +"""Tool names excluded from context-editing prune when bound.""" + +from __future__ import annotations + +from collections.abc import Sequence + +from langchain_core.tools import BaseTool + +PRUNE_PROTECTED_TOOL_NAMES: frozenset[str] = frozenset( + { + "generate_report", + "generate_resume", + "generate_podcast", + "generate_video_presentation", + "generate_image", + "read_email", + "search_emails", + "invalid", + }, +) + + +def safe_exclude_tools(tools: Sequence[BaseTool]) -> tuple[str, ...]: + """Names from ``PRUNE_PROTECTED_TOOL_NAMES`` that appear in ``tools``.""" + enabled = {t.name for t in tools} + return tuple(n for n in PRUNE_PROTECTED_TOOL_NAMES if n in enabled) diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/__init__.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/__init__.py new file mode 100644 index 000000000..bff255bb4 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/__init__.py @@ -0,0 +1,13 @@ +"""Connector-gated tool deny rules and small permission helpers for the main-agent graph.""" + +from __future__ import annotations + +from .connector_deny_rules import synthesize_connector_deny_rules +from .connector_gated_tool_names import iter_connector_gated_tools +from .rule import Rule + +__all__ = [ + "Rule", + "iter_connector_gated_tools", + "synthesize_connector_deny_rules", +] diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_deny_rules.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_deny_rules.py new file mode 100644 index 000000000..2144fc7de --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_deny_rules.py @@ -0,0 +1,21 @@ +"""Synthesized PermissionMiddleware deny rules for tools gated by connector.""" + +from __future__ import annotations + +from .connector_gated_tool_names import iter_connector_gated_tools +from .rule import Rule + + +def synthesize_connector_deny_rules( + *, + available_connectors: list[str] | None, + enabled_tool_names: set[str], +) -> list[Rule]: + available = set(available_connectors or []) + deny: list[Rule] = [] + for name, required in iter_connector_gated_tools(): + if name not in enabled_tool_names: + continue + if required not in available: + deny.append(Rule(permission=name, pattern="*", action="deny")) + return deny diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_gated_tool_names.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_gated_tool_names.py new file mode 100644 index 000000000..0b5b2bcd7 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/connector_gated_tool_names.py @@ -0,0 +1,42 @@ +"""Tool name → required searchable connector type (keep in sync with new_chat ``BUILTIN_TOOLS``).""" + +from __future__ import annotations + +# Synced from ``app.agents.new_chat.tools.registry`` ToolDefinition.required_connector entries. +_CONNECTOR_GATED: tuple[tuple[str, str], ...] = ( + ("create_notion_page", "NOTION_CONNECTOR"), + ("update_notion_page", "NOTION_CONNECTOR"), + ("delete_notion_page", "NOTION_CONNECTOR"), + ("create_google_drive_file", "GOOGLE_DRIVE_FILE"), + ("delete_google_drive_file", "GOOGLE_DRIVE_FILE"), + ("create_dropbox_file", "DROPBOX_FILE"), + ("delete_dropbox_file", "DROPBOX_FILE"), + ("create_onedrive_file", "ONEDRIVE_FILE"), + ("delete_onedrive_file", "ONEDRIVE_FILE"), + ("search_calendar_events", "GOOGLE_CALENDAR_CONNECTOR"), + ("create_calendar_event", "GOOGLE_CALENDAR_CONNECTOR"), + ("update_calendar_event", "GOOGLE_CALENDAR_CONNECTOR"), + ("delete_calendar_event", "GOOGLE_CALENDAR_CONNECTOR"), + ("search_gmail", "GOOGLE_GMAIL_CONNECTOR"), + ("read_gmail_email", "GOOGLE_GMAIL_CONNECTOR"), + ("create_gmail_draft", "GOOGLE_GMAIL_CONNECTOR"), + ("send_gmail_email", "GOOGLE_GMAIL_CONNECTOR"), + ("trash_gmail_email", "GOOGLE_GMAIL_CONNECTOR"), + ("update_gmail_draft", "GOOGLE_GMAIL_CONNECTOR"), + ("create_confluence_page", "CONFLUENCE_CONNECTOR"), + ("update_confluence_page", "CONFLUENCE_CONNECTOR"), + ("delete_confluence_page", "CONFLUENCE_CONNECTOR"), + ("list_discord_channels", "DISCORD_CONNECTOR"), + ("read_discord_messages", "DISCORD_CONNECTOR"), + ("send_discord_message", "DISCORD_CONNECTOR"), + ("list_teams_channels", "TEAMS_CONNECTOR"), + ("read_teams_messages", "TEAMS_CONNECTOR"), + ("send_teams_message", "TEAMS_CONNECTOR"), + ("list_luma_events", "LUMA_CONNECTOR"), + ("read_luma_event", "LUMA_CONNECTOR"), + ("create_luma_event", "LUMA_CONNECTOR"), +) + + +def iter_connector_gated_tools() -> tuple[tuple[str, str], ...]: + return _CONNECTOR_GATED diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/rule.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/rule.py new file mode 100644 index 000000000..b59e76083 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/permissions/rule.py @@ -0,0 +1,15 @@ +"""Minimal permission rule type (mirrors OpenCode semantics used by PermissionMiddleware).""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal + +RuleAction = Literal["allow", "deny", "ask"] + + +@dataclass(frozen=True) +class Rule: + permission: str + pattern: str + action: RuleAction diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/__init__.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/__init__.py new file mode 100644 index 000000000..914257521 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/__init__.py @@ -0,0 +1,7 @@ +"""Main-agent SurfSense tool allowlist.""" + +from __future__ import annotations + +from .index import MAIN_AGENT_SURFSENSE_TOOL_NAMES, MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED + +__all__ = ["MAIN_AGENT_SURFSENSE_TOOL_NAMES", "MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED"] diff --git a/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/index.py b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/index.py new file mode 100644 index 000000000..5d309261c --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_with_deepagents/main_agent/tools/index.py @@ -0,0 +1,17 @@ +"""Main-agent SurfSense builtin tool names (not full ``new_chat``). + +Connector integrations, MCP, deliverables, etc. are delegated via ``task`` subagents. +""" + +from __future__ import annotations + +MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED: tuple[str, ...] = ( + "search_surfsense_docs", + "web_search", + "scrape_webpage", + "update_memory", +) + +MAIN_AGENT_SURFSENSE_TOOL_NAMES: frozenset[str] = frozenset( + MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED, +)