mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-08 10:47:44 +02:00
[eric] agents: SDK deny list merges effective_disallowed + hard blocks (plain reassignment silently discarded the computed denies; runtime gate was the only wall)
This commit is contained in:
@@ -29,7 +29,7 @@ from backend.apps.agents.manager.prompt.tool_catalog import get_all_tool_names
|
||||
from backend.apps.agents.manager.prompt.prompt_context import resolve_mode
|
||||
from backend.apps.agents.manager.run.run_options_helpers import (
|
||||
pre_send_context_guard, set_framework_overhead, register_web_mcp_server,
|
||||
append_web_tools_hint, inject_thinking_options,
|
||||
append_web_tools_hint, inject_thinking_options, merge_hard_blocked_tools,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -227,12 +227,8 @@ class RunOptions(AgentManagerProtocol):
|
||||
options_kwargs["extra_args"] = p_ea
|
||||
|
||||
# The claude_code preset auto-attaches the user's claude.ai- connected partner MCPs (`mcp__claude_ai_*`). Those bypass our MCPActivate gate, don't share OAuth state with the OpenSwarm Gmail/Calendar/Drive connectors the user actually configured here, and confuse the model into picking the partner shim instead of our vetted server. Hard-block them at the SDK layer so the model can't even attempt the call.
|
||||
options_kwargs["disallowed_tools"] = [
|
||||
"mcp__claude_ai_*",
|
||||
# The CLI's built-in sub-agent tool is replaced by our SpawnAgent MCP (prompt + run_in_background only); it is named Task on CLI 2.1.122 (Agent on older builds, kept for safety), and its subagent types resolve to models router setups can't serve.
|
||||
"Agent",
|
||||
"Task",
|
||||
]
|
||||
# merge EXTENDS effective_disallowed: the old plain assignment silently discarded the computed denies (Cron*/Skill/web-swap/per-tool MCP) and left the runtime gate as the only wall.
|
||||
options_kwargs["disallowed_tools"] = merge_hard_blocked_tools(effective_disallowed)
|
||||
|
||||
if session.cwd:
|
||||
# Pre-existing sessions may have workspaces that predate the git-init block in launch_agent, leaving them without a valid HEAD. Ensure it here so subagent worktree-add always works.
|
||||
|
||||
@@ -12,6 +12,15 @@ from backend.apps.agents.manager.session.history_compaction import estimate_post
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Always SDK-blocked regardless of permissions: claude.ai partner MCPs bypass our MCPActivate gate, and the CLI's built-in sub-agent tool (Task on 2.1.122, Agent on older builds) is replaced by our SpawnAgent MCP.
|
||||
HARD_BLOCKED_TOOLS: List[str] = ["mcp__claude_ai_*", "Agent", "Task"]
|
||||
|
||||
|
||||
@typechecked
|
||||
def merge_hard_blocked_tools(effective_disallowed: List[str]) -> List[str]:
|
||||
"""The SDK deny list = the computed per-turn denies PLUS the unconditional hard blocks. A plain assignment here once silently discarded effective_disallowed (Cron*/Skill/web-swap/per-tool MCP denies), leaving the runtime gate as the only wall; merge, never overwrite."""
|
||||
return effective_disallowed + [t for t in HARD_BLOCKED_TOOLS if t not in effective_disallowed]
|
||||
|
||||
|
||||
# `manager` is the AgentManager; it isn't annotated because typing it would import agent_manager back into a module agent_manager already imports (a cycle). Same reason self is never annotated.
|
||||
@typechecked
|
||||
|
||||
@@ -73,13 +73,23 @@ def test_spawn_agent_unknown_parent_raises() -> None:
|
||||
def test_builtin_subagent_tools_stay_blocked() -> None:
|
||||
# The CLI's built-in sub-agent tool (Task on 2.1.122, Agent on older builds) must not be offered: out of the catalog AND hard-blocked at the SDK layer.
|
||||
from backend.apps.agents.manager.prompt.tool_catalog import FULL_TOOLS
|
||||
from backend.apps.agents.manager.run.run_options_helpers import merge_hard_blocked_tools
|
||||
assert "Agent" not in FULL_TOOLS
|
||||
assert "Task" not in FULL_TOOLS
|
||||
import inspect
|
||||
from backend.apps.agents.manager.run import RunOptions
|
||||
src = inspect.getsource(RunOptions)
|
||||
block = src.split('disallowed_tools"] = [')[1][:400]
|
||||
assert '"Agent",' in block and '"Task",' in block
|
||||
blocked = merge_hard_blocked_tools([])
|
||||
assert "Agent" in blocked and "Task" in blocked and "mcp__claude_ai_*" in blocked
|
||||
|
||||
|
||||
def test_effective_disallowed_reaches_the_sdk_deny_list() -> None:
|
||||
# Regression: a plain assignment once DISCARDED the computed denies (Cron*/Skill/web-swap/per-tool MCP), leaving the runtime gate as the only wall. The merge must keep them AND append the hard blocks, deduped.
|
||||
from backend.apps.agents.manager.run.run_options_helpers import merge_hard_blocked_tools
|
||||
computed = ["CronCreate", "CronList", "CronDelete", "Skill", "mcp__gmail__DeleteEmail", "Task"]
|
||||
merged = merge_hard_blocked_tools(computed)
|
||||
for t in computed:
|
||||
assert t in merged
|
||||
assert "Agent" in merged and "mcp__claude_ai_*" in merged
|
||||
assert merged.count("Task") == 1
|
||||
assert merged[:len(computed)] == computed
|
||||
|
||||
|
||||
def test_spawn_server_schema_is_prompt_plus_background_only() -> None:
|
||||
|
||||
Reference in New Issue
Block a user