multi_agent_chat/connectors: every route declares its own RULESET + flat load_tools()

This commit is contained in:
CREDO23 2026-05-14 20:09:49 +02:00
parent d45dfbfbd6
commit 3bb90124d2
30 changed files with 588 additions and 800 deletions

View file

@ -1,29 +1,22 @@
"""`airtable` route: ``SubAgent`` spec for deepagents."""
"""``airtable`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools come exclusively from MCP. The connector's own approval ruleset is
declared in :data:`tools.index.RULESET`; the orchestrator layers it into
a per-subagent :class:`PermissionMiddleware`.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "airtable"
from .tools.index import NAME, RULESET
def build_subagent(
@ -31,26 +24,20 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles airtable tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
description = (
read_md_file(__package__, "description").strip()
or "Handles airtable tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
tools=list(mcp_tools or []),
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,27 +1,21 @@
"""``airtable`` permission ruleset (rules over MCP tool names)."""
from __future__ import annotations
from typing import Any
from app.agents.new_chat.permissions import Rule, Ruleset
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
NAME = "airtable"
RULESET = Ruleset(
origin=NAME,
rules=[
Rule(permission="list_bases", pattern="*", action="allow"),
Rule(permission="search_bases", pattern="*", action="allow"),
Rule(permission="list_tables_for_base", pattern="*", action="allow"),
Rule(permission="get_table_schema", pattern="*", action="allow"),
Rule(permission="list_records_for_table", pattern="*", action="allow"),
Rule(permission="search_records", pattern="*", action="allow"),
Rule(permission="create_records_for_table", pattern="*", action="ask"),
Rule(permission="update_records_for_table", pattern="*", action="ask"),
],
)
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
_ = {**(dependencies or {}), **kwargs}
return {
"allow": [
{"name": "list_bases"},
{"name": "search_bases"},
{"name": "list_tables_for_base"},
{"name": "get_table_schema"},
{"name": "list_records_for_table"},
{"name": "search_records"},
],
"ask": [
{"name": "create_records_for_table"},
{"name": "update_records_for_table"},
],
}

View file

@ -1,29 +1,22 @@
"""`calendar` route: ``SubAgent`` spec for deepagents."""
"""``calendar`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity with MCP-backed connectors.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "calendar"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles calendar tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles calendar tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,38 +1,39 @@
"""``calendar`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies, so the
ruleset just falls through to the SurfSense allow-by-default rules.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_event import create_create_calendar_event_tool
from .delete_event import create_delete_calendar_event_tool
from .search_events import create_search_calendar_events_tool
from .update_event import create_update_calendar_event_tool
NAME = "calendar"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
resolved_dependencies = {**(dependencies or {}), **kwargs}
session_dependencies = {
"db_session": resolved_dependencies["db_session"],
"search_space_id": resolved_dependencies["search_space_id"],
"user_id": resolved_dependencies["user_id"],
}
search = create_search_calendar_events_tool(**session_dependencies)
create = create_create_calendar_event_tool(**session_dependencies)
update = create_update_calendar_event_tool(**session_dependencies)
delete = create_delete_calendar_event_tool(**session_dependencies)
return {
"allow": [self_gated_tool_permission_row(search)],
"ask": [
self_gated_tool_permission_row(create),
self_gated_tool_permission_row(update),
self_gated_tool_permission_row(delete),
],
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
return [
create_search_calendar_events_tool(**common),
create_create_calendar_event_tool(**common),
create_update_calendar_event_tool(**common),
create_delete_calendar_event_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`clickup` route: ``SubAgent`` spec for deepagents."""
"""``clickup`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools come exclusively from MCP. The connector's own approval ruleset is
declared in :data:`tools.index.RULESET`; the orchestrator layers it into
a per-subagent :class:`PermissionMiddleware`.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "clickup"
from .tools.index import NAME, RULESET
def build_subagent(
@ -31,26 +24,20 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles clickup tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
description = (
read_md_file(__package__, "description").strip()
or "Handles clickup tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
tools=list(mcp_tools or []),
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,26 +1,20 @@
"""``clickup`` permission ruleset (rules over MCP tool names)."""
from __future__ import annotations
from typing import Any
from app.agents.new_chat.permissions import Rule, Ruleset
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
NAME = "clickup"
RULESET = Ruleset(
origin=NAME,
rules=[
Rule(permission="clickup_search", pattern="*", action="allow"),
Rule(permission="clickup_get_task", pattern="*", action="allow"),
Rule(permission="clickup_get_workspace_hierarchy", pattern="*", action="allow"),
Rule(permission="clickup_get_list", pattern="*", action="allow"),
Rule(permission="clickup_find_member_by_name", pattern="*", action="allow"),
Rule(permission="clickup_create_task", pattern="*", action="ask"),
Rule(permission="clickup_update_task", pattern="*", action="ask"),
],
)
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
_ = {**(dependencies or {}), **kwargs}
return {
"allow": [
{"name": "clickup_search"},
{"name": "clickup_get_task"},
{"name": "clickup_get_workspace_hierarchy"},
{"name": "clickup_get_list"},
{"name": "clickup_find_member_by_name"},
],
"ask": [
{"name": "clickup_create_task"},
{"name": "clickup_update_task"},
],
}

View file

@ -1,29 +1,22 @@
"""`confluence` route: ``SubAgent`` spec for deepagents."""
"""``confluence`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "confluence"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles confluence tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles confluence tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,37 +1,37 @@
"""``confluence`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_page import create_create_confluence_page_tool
from .delete_page import create_delete_confluence_page_tool
from .update_page import create_update_confluence_page_tool
NAME = "confluence"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
resolved_dependencies = {**(dependencies or {}), **kwargs}
session_dependencies = {
"db_session": resolved_dependencies["db_session"],
"search_space_id": resolved_dependencies["search_space_id"],
"user_id": resolved_dependencies["user_id"],
"connector_id": resolved_dependencies.get("connector_id"),
}
create = create_create_confluence_page_tool(**session_dependencies)
update = create_update_confluence_page_tool(**session_dependencies)
delete = create_delete_confluence_page_tool(**session_dependencies)
return {
"allow": [],
"ask": [
self_gated_tool_permission_row(create),
self_gated_tool_permission_row(update),
self_gated_tool_permission_row(delete),
],
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
"connector_id": d.get("connector_id"),
}
return [
create_create_confluence_page_tool(**common),
create_update_confluence_page_tool(**common),
create_delete_confluence_page_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`discord` route: ``SubAgent`` spec for deepagents."""
"""``discord`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "discord"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles discord tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles discord tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,35 +1,36 @@
"""``discord`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .list_channels import create_list_discord_channels_tool
from .read_messages import create_read_discord_messages_tool
from .send_message import create_send_discord_message_tool
NAME = "discord"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
list_ch = create_list_discord_channels_tool(**common)
read_msg = create_read_discord_messages_tool(**common)
send = create_send_discord_message_tool(**common)
return {
"allow": [
self_gated_tool_permission_row(list_ch),
self_gated_tool_permission_row(read_msg),
],
"ask": [self_gated_tool_permission_row(send)],
}
return [
create_list_discord_channels_tool(**common),
create_read_discord_messages_tool(**common),
create_send_discord_message_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`dropbox` route: ``SubAgent`` spec for deepagents."""
"""``dropbox`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "dropbox"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles dropbox tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles dropbox tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,33 +1,34 @@
"""``dropbox`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_file import create_create_dropbox_file_tool
from .trash_file import create_delete_dropbox_file_tool
NAME = "dropbox"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
create = create_create_dropbox_file_tool(**common)
delete = create_delete_dropbox_file_tool(**common)
return {
"allow": [],
"ask": [
self_gated_tool_permission_row(create),
self_gated_tool_permission_row(delete),
],
}
return [
create_create_dropbox_file_tool(**common),
create_delete_dropbox_file_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`gmail` route: ``SubAgent`` spec for deepagents."""
"""``gmail`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "gmail"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles gmail tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles gmail tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,13 +1,15 @@
"""``gmail`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_draft import create_create_gmail_draft_tool
from .read_email import create_read_gmail_email_tool
@ -16,31 +18,25 @@ from .send_email import create_send_gmail_email_tool
from .trash_email import create_trash_gmail_email_tool
from .update_draft import create_update_gmail_draft_tool
NAME = "gmail"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
search = create_search_gmail_tool(**common)
read = create_read_gmail_email_tool(**common)
draft = create_create_gmail_draft_tool(**common)
send = create_send_gmail_email_tool(**common)
trash = create_trash_gmail_email_tool(**common)
updraft = create_update_gmail_draft_tool(**common)
return {
"allow": [
self_gated_tool_permission_row(search),
self_gated_tool_permission_row(read),
],
"ask": [
self_gated_tool_permission_row(draft),
self_gated_tool_permission_row(send),
self_gated_tool_permission_row(trash),
self_gated_tool_permission_row(updraft),
],
}
return [
create_search_gmail_tool(**common),
create_read_gmail_email_tool(**common),
create_create_gmail_draft_tool(**common),
create_send_gmail_email_tool(**common),
create_trash_gmail_email_tool(**common),
create_update_gmail_draft_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`google_drive` route: ``SubAgent`` spec for deepagents."""
"""``google_drive`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "google_drive"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles google drive tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles google drive tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,33 +1,34 @@
"""``google_drive`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_file import create_create_google_drive_file_tool
from .trash_file import create_delete_google_drive_file_tool
NAME = "google_drive"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
create = create_create_google_drive_file_tool(**common)
delete = create_delete_google_drive_file_tool(**common)
return {
"allow": [],
"ask": [
self_gated_tool_permission_row(create),
self_gated_tool_permission_row(delete),
],
}
return [
create_create_google_drive_file_tool(**common),
create_delete_google_drive_file_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`jira` route: ``SubAgent`` spec for deepagents."""
"""``jira`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools come exclusively from MCP. The connector's own approval ruleset is
declared in :data:`tools.index.RULESET`; the orchestrator layers it into
a per-subagent :class:`PermissionMiddleware`.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "jira"
from .tools.index import NAME, RULESET
def build_subagent(
@ -31,26 +24,20 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles jira tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
description = (
read_md_file(__package__, "description").strip()
or "Handles jira tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
tools=list(mcp_tools or []),
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,30 +1,24 @@
"""``jira`` permission ruleset (rules over MCP tool names)."""
from __future__ import annotations
from typing import Any
from app.agents.new_chat.permissions import Rule, Ruleset
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
NAME = "jira"
RULESET = Ruleset(
origin=NAME,
rules=[
Rule(permission="getAccessibleAtlassianResources", pattern="*", action="allow"),
Rule(permission="getVisibleJiraProjects", pattern="*", action="allow"),
Rule(permission="searchJiraIssuesUsingJql", pattern="*", action="allow"),
Rule(permission="getJiraIssue", pattern="*", action="allow"),
Rule(permission="getJiraProjectIssueTypesMetadata", pattern="*", action="allow"),
Rule(permission="getJiraIssueTypeMetaWithFields", pattern="*", action="allow"),
Rule(permission="getTransitionsForJiraIssue", pattern="*", action="allow"),
Rule(permission="lookupJiraAccountId", pattern="*", action="allow"),
Rule(permission="createJiraIssue", pattern="*", action="ask"),
Rule(permission="editJiraIssue", pattern="*", action="ask"),
Rule(permission="transitionJiraIssue", pattern="*", action="ask"),
],
)
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
_ = {**(dependencies or {}), **kwargs}
return {
"allow": [
{"name": "getAccessibleAtlassianResources"},
{"name": "getVisibleJiraProjects"},
{"name": "searchJiraIssuesUsingJql"},
{"name": "getJiraIssue"},
{"name": "getJiraProjectIssueTypesMetadata"},
{"name": "getJiraIssueTypeMetaWithFields"},
{"name": "getTransitionsForJiraIssue"},
{"name": "lookupJiraAccountId"},
],
"ask": [
{"name": "createJiraIssue"},
{"name": "editJiraIssue"},
{"name": "transitionJiraIssue"},
],
}

View file

@ -1,29 +1,22 @@
"""`linear` route: ``SubAgent`` spec for deepagents."""
"""``linear`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools come exclusively from MCP. The connector's own approval ruleset is
declared in :data:`tools.index.RULESET`; the orchestrator layers it into
a per-subagent :class:`PermissionMiddleware`.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "linear"
from .tools.index import NAME, RULESET
def build_subagent(
@ -31,26 +24,20 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles linear tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
description = (
read_md_file(__package__, "description").strip()
or "Handles linear tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
tools=list(mcp_tools or []),
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,37 +1,31 @@
"""``linear`` permission ruleset (rules over MCP tool names)."""
from __future__ import annotations
from typing import Any
from app.agents.new_chat.permissions import Rule, Ruleset
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
NAME = "linear"
RULESET = Ruleset(
origin=NAME,
rules=[
Rule(permission="list_issues", pattern="*", action="allow"),
Rule(permission="get_issue", pattern="*", action="allow"),
Rule(permission="list_my_issues", pattern="*", action="allow"),
Rule(permission="list_issue_statuses", pattern="*", action="allow"),
Rule(permission="list_issue_labels", pattern="*", action="allow"),
Rule(permission="list_comments", pattern="*", action="allow"),
Rule(permission="list_users", pattern="*", action="allow"),
Rule(permission="get_user", pattern="*", action="allow"),
Rule(permission="list_teams", pattern="*", action="allow"),
Rule(permission="get_team", pattern="*", action="allow"),
Rule(permission="list_projects", pattern="*", action="allow"),
Rule(permission="get_project", pattern="*", action="allow"),
Rule(permission="list_project_labels", pattern="*", action="allow"),
Rule(permission="list_cycles", pattern="*", action="allow"),
Rule(permission="list_documents", pattern="*", action="allow"),
Rule(permission="get_document", pattern="*", action="allow"),
Rule(permission="search_documentation", pattern="*", action="allow"),
Rule(permission="save_issue", pattern="*", action="ask"),
],
)
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
_ = {**(dependencies or {}), **kwargs}
return {
"allow": [
{"name": "list_issues"},
{"name": "get_issue"},
{"name": "list_my_issues"},
{"name": "list_issue_statuses"},
{"name": "list_issue_labels"},
{"name": "list_comments"},
{"name": "list_users"},
{"name": "get_user"},
{"name": "list_teams"},
{"name": "get_team"},
{"name": "list_projects"},
{"name": "get_project"},
{"name": "list_project_labels"},
{"name": "list_cycles"},
{"name": "list_documents"},
{"name": "get_document"},
{"name": "search_documentation"},
],
"ask": [
{"name": "save_issue"}
],
}

View file

@ -1,29 +1,22 @@
"""`luma` route: ``SubAgent`` spec for deepagents."""
"""``luma`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "luma"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles luma tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles luma tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,35 +1,36 @@
"""``luma`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_event import create_create_luma_event_tool
from .list_events import create_list_luma_events_tool
from .read_event import create_read_luma_event_tool
NAME = "luma"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
list_ev = create_list_luma_events_tool(**common)
read_ev = create_read_luma_event_tool(**common)
create = create_create_luma_event_tool(**common)
return {
"allow": [
self_gated_tool_permission_row(list_ev),
self_gated_tool_permission_row(read_ev),
],
"ask": [self_gated_tool_permission_row(create)],
}
return [
create_list_luma_events_tool(**common),
create_read_luma_event_tool(**common),
create_create_luma_event_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`notion` route: ``SubAgent`` spec for deepagents."""
"""``notion`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "notion"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles notion tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles notion tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,36 +1,36 @@
"""``notion`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_page import create_create_notion_page_tool
from .delete_page import create_delete_notion_page_tool
from .update_page import create_update_notion_page_tool
NAME = "notion"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
create = create_create_notion_page_tool(**common)
update = create_update_notion_page_tool(**common)
delete = create_delete_notion_page_tool(**common)
return {
"allow": [],
"ask": [
self_gated_tool_permission_row(create),
self_gated_tool_permission_row(update),
self_gated_tool_permission_row(delete),
],
}
return [
create_create_notion_page_tool(**common),
create_update_notion_page_tool(**common),
create_delete_notion_page_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`onedrive` route: ``SubAgent`` spec for deepagents."""
"""``onedrive`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "onedrive"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles onedrive tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles onedrive tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,33 +1,34 @@
"""``onedrive`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .create_file import create_create_onedrive_file_tool
from .trash_file import create_delete_onedrive_file_tool
NAME = "onedrive"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
create = create_create_onedrive_file_tool(**common)
delete = create_delete_onedrive_file_tool(**common)
return {
"allow": [],
"ask": [
self_gated_tool_permission_row(create),
self_gated_tool_permission_row(delete),
],
}
return [
create_create_onedrive_file_tool(**common),
create_delete_onedrive_file_tool(**common),
]

View file

@ -1,29 +1,22 @@
"""`slack` route: ``SubAgent`` spec for deepagents."""
"""``slack`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools come exclusively from MCP. The connector's own approval ruleset is
declared in :data:`tools.index.RULESET`; the orchestrator layers it into
a per-subagent :class:`PermissionMiddleware`.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "slack"
from .tools.index import NAME, RULESET
def build_subagent(
@ -31,26 +24,20 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles slack tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
description = (
read_md_file(__package__, "description").strip()
or "Handles slack tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
tools=list(mcp_tools or []),
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,25 +1,19 @@
"""``slack`` permission ruleset (rules over MCP tool names)."""
from __future__ import annotations
from typing import Any
from app.agents.new_chat.permissions import Rule, Ruleset
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
NAME = "slack"
RULESET = Ruleset(
origin=NAME,
rules=[
Rule(permission="slack_search_channels", pattern="*", action="allow"),
Rule(permission="slack_search_messages", pattern="*", action="allow"),
Rule(permission="slack_search_users", pattern="*", action="allow"),
Rule(permission="slack_read_channel", pattern="*", action="allow"),
Rule(permission="slack_read_thread", pattern="*", action="allow"),
Rule(permission="slack_send_message", pattern="*", action="ask"),
],
)
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
_ = {**(dependencies or {}), **kwargs}
return {
"allow": [
{"name": "slack_search_channels"},
{"name": "slack_search_messages"},
{"name": "slack_search_users"},
{"name": "slack_read_channel"},
{"name": "slack_read_thread"},
],
"ask": [
{"name": "slack_send_message"},
],
}

View file

@ -1,29 +1,22 @@
"""`teams` route: ``SubAgent`` spec for deepagents."""
"""``teams`` route: ``SurfSenseSubagentSpec`` builder for deepagents.
Tools self-gate inside their bodies via :func:`request_approval`; the
empty :data:`tools.index.RULESET` is layered into a per-subagent
:class:`PermissionMiddleware` for uniformity.
"""
from __future__ import annotations
from typing import Any
from deepagents import SubAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.middleware_gated import (
middleware_gated_interrupt_on,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import (
read_md_file,
)
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import (
pack_subagent,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
merge_tools_permissions,
)
from app.agents.multi_agent_chat.subagents.shared.md_file_reader import read_md_file
from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec
from app.agents.multi_agent_chat.subagents.shared.subagent_builder import pack_subagent
from .tools.index import load_tools
NAME = "teams"
from .tools.index import NAME, RULESET, load_tools
def build_subagent(
@ -31,26 +24,21 @@ def build_subagent(
dependencies: dict[str, Any],
model: BaseChatModel | None = None,
middleware_stack: dict[str, Any] | None = None,
extra_tools_bucket: ToolsPermissions | None = None,
) -> SubAgent:
buckets = load_tools(dependencies=dependencies)
merged_tools_bucket = merge_tools_permissions(buckets, extra_tools_bucket)
tools = [
row["tool"]
for row in (*merged_tools_bucket["allow"], *merged_tools_bucket["ask"])
if row.get("tool") is not None
]
interrupt_on = middleware_gated_interrupt_on(merged_tools_bucket)
description = read_md_file(__package__, "description").strip()
if not description:
description = "Handles teams tasks for this workspace."
mcp_tools: list[BaseTool] | None = None,
) -> SurfSenseSubagentSpec:
tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])]
description = (
read_md_file(__package__, "description").strip()
or "Handles teams tasks for this workspace."
)
system_prompt = read_md_file(__package__, "system_prompt").strip()
return pack_subagent(
name=NAME,
description=description,
system_prompt=system_prompt,
tools=tools,
interrupt_on=interrupt_on,
ruleset=RULESET,
flags=dependencies["flags"],
model=model,
middleware_stack=middleware_stack,
)

View file

@ -1,35 +1,36 @@
"""``teams`` native tools and (empty) permission ruleset.
Tools self-gate via :func:`request_approval` in their bodies.
"""
from __future__ import annotations
from typing import Any
from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import (
self_gated_tool_permission_row,
)
from app.agents.multi_agent_chat.subagents.shared.tool_kinds import (
ToolsPermissions,
)
from langchain_core.tools import BaseTool
from app.agents.new_chat.permissions import Ruleset
from .list_channels import create_list_teams_channels_tool
from .read_messages import create_read_teams_messages_tool
from .send_message import create_send_teams_message_tool
NAME = "teams"
RULESET = Ruleset(origin=NAME, rules=[])
def load_tools(
*, dependencies: dict[str, Any] | None = None, **kwargs: Any
) -> ToolsPermissions:
) -> list[BaseTool]:
d = {**(dependencies or {}), **kwargs}
common = {
"db_session": d["db_session"],
"search_space_id": d["search_space_id"],
"user_id": d["user_id"],
}
list_ch = create_list_teams_channels_tool(**common)
read_msg = create_read_teams_messages_tool(**common)
send = create_send_teams_message_tool(**common)
return {
"allow": [
self_gated_tool_permission_row(list_ch),
self_gated_tool_permission_row(read_msg),
],
"ask": [self_gated_tool_permission_row(send)],
}
return [
create_list_teams_channels_tool(**common),
create_read_teams_messages_tool(**common),
create_send_teams_message_tool(**common),
]