feat: remvoe other default tools

This commit is contained in:
Richard Tang
2026-05-01 13:35:04 -07:00
parent c147364d8c
commit 6f2f037c9c
4 changed files with 137 additions and 6 deletions
@@ -169,14 +169,16 @@ _TOOL_CATEGORIES: dict[str, list[str]] = {
# user-added custom queen IDs that we don't know about.
QUEEN_DEFAULT_CATEGORIES: dict[str, list[str]] = {
# Head of Technology — builds and operates systems; full toolkit.
# Head of Technology — builds and operates systems. Security tools
# (port_scan, subdomain_enumerate, etc.) are intentionally NOT in the
# default — users opt in via the Tool Library when an engagement
# actually needs reconnaissance.
"queen_technology": [
"file_ops",
"terminal_basic",
"browser_basic",
"browser_interaction",
"research",
"security",
"time_context",
],
# Head of Growth — data, experiments, competitor research; no security.
@@ -253,6 +255,49 @@ def has_role_default(queen_id: str) -> bool:
return queen_id in QUEEN_DEFAULT_CATEGORIES
def list_category_names() -> list[str]:
"""Return every category name defined in the table, in declaration order."""
return list(_TOOL_CATEGORIES.keys())
def queen_role_categories(queen_id: str) -> list[str]:
"""Return the category names assigned to ``queen_id`` by role default.
Returns an empty list for queens not in the persona table (they fall
through to allow-all and have no implicit category membership).
"""
return list(QUEEN_DEFAULT_CATEGORIES.get(queen_id, []))
def resolve_category_tools(
category: str,
mcp_catalog: dict[str, list[dict[str, Any]]] | None = None,
) -> list[str]:
"""Expand a single category to its concrete tool names.
Mirrors ``resolve_queen_default_tools`` but for a single category, so
callers (e.g. the Tool Library API) can present per-category tool
membership without re-implementing the ``@server:NAME`` shorthand
expansion.
"""
names: list[str] = []
seen: set[str] = set()
for entry in _TOOL_CATEGORIES.get(category, []):
if entry.startswith("@server:"):
server_name = entry[len("@server:") :]
if mcp_catalog is None:
continue
for tool in mcp_catalog.get(server_name, []) or []:
tname = tool.get("name") if isinstance(tool, dict) else None
if tname and tname not in seen:
seen.add(tname)
names.append(tname)
elif entry not in seen:
seen.add(entry)
names.append(entry)
return names
def resolve_queen_default_tools(
queen_id: str,
mcp_catalog: dict[str, list[dict[str, Any]]] | None = None,
@@ -35,6 +35,11 @@ from framework.agents.queen.queen_tools_config import (
tools_config_exists,
update_queen_tools_config,
)
from framework.agents.queen.queen_tools_defaults import (
list_category_names,
queen_role_categories,
resolve_category_tools,
)
logger = logging.getLogger(__name__)
@@ -326,10 +331,36 @@ async def handle_get_tools(request: web.Request) -> web.Response:
mcp_tool_names_by_server=catalog,
enabled_mcp_tools=enabled_mcp_tools,
),
"categories": _render_categories(queen_id, catalog),
}
return web.json_response(response)
def _render_categories(
queen_id: str,
mcp_catalog: dict[str, list[dict[str, Any]]],
) -> list[dict[str, Any]]:
"""Expose the role-default category table to the frontend.
Each entry carries the category name, the resolved member tool names
(after ``@server:NAME`` shorthand expansion against the live catalog),
and ``in_role_default`` to flag categories that contribute to this
queen's role-based default. Lets the Tool Library group tools by
category alongside the per-server view.
"""
applied = set(queen_role_categories(queen_id))
out: list[dict[str, Any]] = []
for name in list_category_names():
out.append(
{
"name": name,
"tools": resolve_category_tools(name, mcp_catalog),
"in_role_default": name in applied,
}
)
return out
async def handle_patch_tools(request: web.Request) -> web.Response:
"""PATCH /api/queen/{queen_id}/tools — persist the MCP tool allowlist.
@@ -217,6 +217,41 @@ async def test_get_tools_applies_role_default(queen_dir, monkeypatch):
assert "fluffy_unknown_tool" not in enabled
@pytest.mark.asyncio
async def test_get_tools_exposes_categories(queen_dir, monkeypatch):
"""Response includes the category catalog with role-default flags."""
monkeypatch.setattr(routes_queen_tools, "ensure_default_queens", lambda: None)
_, queen_id = queen_dir # queen_technology
manager = _FakeManager()
manager._mcp_tool_catalog = {
"files-tools": [
{"name": "read_file", "description": "", "input_schema": {}},
{"name": "edit_file", "description": "", "input_schema": {}},
],
}
app = await _make_app(manager=manager)
async with TestClient(TestServer(app)) as client:
resp = await client.get(f"/api/queen/{queen_id}/tools")
assert resp.status == 200
body = await resp.json()
cats = {c["name"]: c for c in body["categories"]}
# Categories that contribute to queen_technology's role default
assert cats["file_ops"]["in_role_default"] is True
assert cats["browser_basic"]["in_role_default"] is True
# Spreadsheet category is exposed even though queen_technology doesn't
# use it — frontend can group/show it.
assert "spreadsheet_advanced" in cats
assert cats["spreadsheet_advanced"]["in_role_default"] is False
# Security was removed from queen_technology defaults.
assert cats["security"]["in_role_default"] is False
# @server:files-tools shorthand expanded against the catalog.
assert "read_file" in cats["file_ops"]["tools"]
assert "edit_file" in cats["file_ops"]["tools"]
def test_resolve_queen_default_tools_expands_server_shorthand():
"""@server:NAME shorthand expands against the provided catalog."""
from framework.agents.queen.queen_tools_defaults import resolve_queen_default_tools
@@ -379,7 +414,10 @@ async def test_delete_restores_role_default(queen_dir, monkeypatch):
manager._mcp_tool_catalog = {
"files-tools": [
{"name": "read_file", "description": "", "input_schema": {}},
{"name": "port_scan", "description": "", "input_schema": {}},
# pdf_read lives in hive_tools but is named explicitly in the
# file_ops category, so we stage it in any server here just to
# surface it through the catalog.
{"name": "pdf_read", "description": "", "input_schema": {}},
],
}
@@ -400,11 +438,14 @@ async def test_delete_restores_role_default(queen_dir, monkeypatch):
assert body["is_role_default"] is True
assert not tools_path.exists()
# The new effective list is the role default for queen_technology,
# which includes both read_file (file_read) and port_scan (security).
# The new effective list is the role default for queen_technology;
# security tools were intentionally removed, so port_scan must NOT
# appear, while file_ops members like read_file/pdf_read do.
enabled = set(body["enabled_mcp_tools"] or [])
assert "read_file" in enabled
assert "port_scan" in enabled
assert "pdf_read" in enabled
assert "port_scan" not in enabled
assert "subdomain_enumerate" not in enabled
# GET confirms.
resp = await client.get(f"/api/queen/{queen_id}/tools")
+14
View File
@@ -28,6 +28,16 @@ export interface McpServerTools {
tools: Array<ToolMeta & { enabled: boolean }>;
}
export interface ToolCategory {
/** Category id (e.g. "spreadsheet_advanced", "browser_basic"). */
name: string;
/** Concrete tool names that belong to this category, after expansion
* of any ``@server:NAME`` shorthands against the live MCP catalog. */
tools: string[];
/** True when this category contributes to the queen's role-based default. */
in_role_default: boolean;
}
export interface QueenToolsResponse {
queen_id: string;
enabled_mcp_tools: string[] | null;
@@ -39,6 +49,10 @@ export interface QueenToolsResponse {
lifecycle: ToolMeta[];
synthetic: ToolMeta[];
mcp_servers: McpServerTools[];
/** Curated category groupings (file_ops, browser_basic, security, …)
* with resolved tool members. ``in_role_default`` flags categories
* baked into this queen's default allowlist. */
categories: ToolCategory[];
}
export interface QueenToolsUpdateResult {