diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/__init__.py index 1d3a5feb0..70d3dfe39 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/__init__.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/__init__.py @@ -5,19 +5,13 @@ from __future__ import annotations 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 app.agents.multi_agent_chat.subagents.shared.tool_kinds import ( - ToolPermissionItem, - ToolsPermissions, - merge_tools_permissions, -) __all__ = [ - "ToolPermissionItem", - "ToolsPermissions", - "merge_tools_permissions", + "SurfSenseSubagentSpec", "pack_subagent", "read_md_file", ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/__init__.py deleted file mode 100644 index e1eb11cbd..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Middleware-gated approval primitives — interception via langchain middlewares. - -Public surface: -- :func:`middleware_gated_tool_permission_row` — tag a tool's row for interception. -- :func:`middleware_gated_interrupt_on` — build the ``interrupt_on`` map fed - into ``HumanInTheLoopMiddleware``. - -The actual ``HumanInTheLoopMiddleware`` and ``PermissionMiddleware`` instances -that consume these helpers live under -``middleware/shared/permissions/`` (rule-engine slice). -""" - -from .interrupt_on import middleware_gated_interrupt_on -from .tool_row import middleware_gated_tool_permission_row - -__all__ = [ - "middleware_gated_interrupt_on", - "middleware_gated_tool_permission_row", -] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/interrupt_on.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/interrupt_on.py deleted file mode 100644 index 4a42676a0..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/interrupt_on.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Build the ``interrupt_on`` map fed into ``HumanInTheLoopMiddleware``. - -The map keys are tool names whose execution must be intercepted before -the call runs. Self-gated rows are intentionally excluded: their bodies -already pause via :func:`request_approval`, and intercepting them too -would double-prompt the user. -""" - -from __future__ import annotations - -from app.agents.multi_agent_chat.subagents.shared.tool_kinds import ToolsPermissions - - -def middleware_gated_interrupt_on(bucket: ToolsPermissions) -> dict[str, bool]: - """``interrupt_on`` map for ``ask`` rows whose bodies don't self-gate.""" - return { - r["name"]: True - for r in bucket["ask"] - if r.get("name") and r.get("kind") == "middleware_gated" - } - - -__all__ = ["middleware_gated_interrupt_on"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/tool_row.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/tool_row.py deleted file mode 100644 index 84c37f523..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/middleware_gated/tool_row.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Row builder tagging a tool for middleware-gated approval. - -Used by MCP tool loading (``mcp_tools/index.py``) so each row carries -``kind="middleware_gated"`` and surfaces in :func:`middleware_gated_interrupt_on`. -Self-gated factories don't call this — they build rows inline with the -default ``kind`` (which collapses to self-gated). -""" - -from __future__ import annotations - -from langchain_core.tools import BaseTool - -from app.agents.multi_agent_chat.subagents.shared.tool_kinds import ( - ToolPermissionItem, -) - - -def middleware_gated_tool_permission_row(tool: BaseTool) -> ToolPermissionItem: - """Build one allow/ask row tagged ``kind="middleware_gated"``.""" - return { - "name": getattr(tool, "name", "") or "", - "tool": tool, - "kind": "middleware_gated", - } - - -__all__ = ["middleware_gated_tool_permission_row"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/__init__.py index 5bf49ce4f..038ec5652 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/__init__.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/__init__.py @@ -2,7 +2,6 @@ Public surface: - :func:`request_approval` — entry point for sensitive tool bodies. -- :func:`self_gated_tool_permission_row` — build an allow/ask row for a self-gated tool. - :class:`HITLResult` — outcome contract. - ``DEFAULT_AUTO_APPROVED_TOOLS`` — safe-by-construction allowlist. """ @@ -10,11 +9,9 @@ Public surface: from .auto_approved import DEFAULT_AUTO_APPROVED_TOOLS from .request import request_approval from .result import HITLResult -from .tool_row import self_gated_tool_permission_row __all__ = [ "DEFAULT_AUTO_APPROVED_TOOLS", "HITLResult", "request_approval", - "self_gated_tool_permission_row", ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/tool_row.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/tool_row.py deleted file mode 100644 index 0560f92b5..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/tool_row.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Row builder for tools that self-gate via :func:`request_approval`. - -The default ``kind`` is omitted on purpose: ``ToolPermissionItem`` defaults -to ``self_gated`` when ``kind`` is absent, so the row stays compact while -keeping the type system honest. Symmetric with -:mod:`hitl.approvals.middleware_gated.tool_row` so connector factories can -read the same way for either kind. -""" - -from __future__ import annotations - -from langchain_core.tools import BaseTool - -from app.agents.multi_agent_chat.subagents.shared.tool_kinds import ( - ToolPermissionItem, -) - - -def self_gated_tool_permission_row(tool: BaseTool) -> ToolPermissionItem: - """Build one allow/ask row for a self-gated tool (body calls ``request_approval``).""" - return {"name": getattr(tool, "name", "") or "", "tool": tool} - - -__all__ = ["self_gated_tool_permission_row"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/tool_kinds.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/tool_kinds.py deleted file mode 100644 index 9b6d824a7..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/tool_kinds.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Cross-kind primitives for tool permission rows. - -Subagents classify their tools into ``allow`` and ``ask`` buckets, and each -row may be either *self-gated* (the tool body calls -:func:`request_approval`) or *middleware-gated* (a middleware intercepts -the call). This module owns the shared types both kinds need: - -- :data:`ToolKind` — the discriminator literal. -- :class:`ToolPermissionItem` — one row in an allow/ask bucket. -- :class:`ToolsPermissions` — the bucket pair. -- :func:`merge_tools_permissions` — concatenates two buckets (typically a - self-gated factory bucket and a middleware-gated MCP bucket). - -Kind-specific helpers live under ``hitl/approvals/`` next to their gating -mechanism: - -- ``hitl/approvals/self_gated/`` — body-level ``request_approval`` primitive. -- ``hitl/approvals/middleware_gated/`` — row builder + ``interrupt_on`` map. -""" - -from __future__ import annotations - -from typing import Literal, NotRequired, TypedDict - -from langchain_core.tools import BaseTool - -ToolKind = Literal["self_gated", "middleware_gated"] - - -class ToolPermissionItem(TypedDict): - """One allow/ask row. - - ``name`` is always set; ``tool`` is present when a bound BaseTool exists - (absent for name-only MCP allow/ask rows). ``kind`` defaults to - ``self_gated`` when absent so existing connector factories keep working - without explicit tagging. - """ - - name: str - tool: NotRequired[BaseTool] - kind: NotRequired[ToolKind] - - -class ToolsPermissions(TypedDict): - """Allow/ask buckets shared by self-gated factories and middleware-gated MCP rows.""" - - allow: list[ToolPermissionItem] - ask: list[ToolPermissionItem] - - -def merge_tools_permissions( - base: ToolsPermissions, - extra: ToolsPermissions | None, -) -> ToolsPermissions: - """Concatenate allow/ask lists (e.g. self-gated factory + middleware-gated MCP) before building HITL maps.""" - if not extra: - return base - return { - "allow": [*base["allow"], *extra["allow"]], - "ask": [*base["ask"], *extra["ask"]], - } - - -__all__ = [ - "ToolKind", - "ToolPermissionItem", - "ToolsPermissions", - "merge_tools_permissions", -]