From 6f2f037c9c583424e25eaa599e086e8406e6fc78 Mon Sep 17 00:00:00 2001 From: Richard Tang Date: Fri, 1 May 2026 13:35:04 -0700 Subject: [PATCH] feat: remvoe other default tools --- .../agents/queen/queen_tools_defaults.py | 49 ++++++++++++++++++- core/framework/server/routes_queen_tools.py | 31 ++++++++++++ .../server/tests/test_queen_tools.py | 49 +++++++++++++++++-- core/frontend/src/api/queens.ts | 14 ++++++ 4 files changed, 137 insertions(+), 6 deletions(-) diff --git a/core/framework/agents/queen/queen_tools_defaults.py b/core/framework/agents/queen/queen_tools_defaults.py index 21de2af9..6af86ea9 100644 --- a/core/framework/agents/queen/queen_tools_defaults.py +++ b/core/framework/agents/queen/queen_tools_defaults.py @@ -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, diff --git a/core/framework/server/routes_queen_tools.py b/core/framework/server/routes_queen_tools.py index 7ff1e511..29b636bf 100644 --- a/core/framework/server/routes_queen_tools.py +++ b/core/framework/server/routes_queen_tools.py @@ -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. diff --git a/core/framework/server/tests/test_queen_tools.py b/core/framework/server/tests/test_queen_tools.py index bed3e39b..a172f450 100644 --- a/core/framework/server/tests/test_queen_tools.py +++ b/core/framework/server/tests/test_queen_tools.py @@ -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") diff --git a/core/frontend/src/api/queens.ts b/core/frontend/src/api/queens.ts index 96149007..617a1b20 100644 --- a/core/frontend/src/api/queens.ts +++ b/core/frontend/src/api/queens.ts @@ -28,6 +28,16 @@ export interface McpServerTools { tools: Array; } +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 {