From 210d5ade1e6351650218357a1d1cfa3fb5d58f20 Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sun, 15 Mar 2026 20:21:21 -0700 Subject: [PATCH] feat(tools): centralize tool emoji metadata in registry + skin integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 'emoji' field to ToolEntry and 'get_emoji()' to ToolRegistry - Add emoji= to all 50+ registry.register() calls across tool files - Add get_tool_emoji() helper in agent/display.py with 3-tier resolution: skin override → registry default → hardcoded fallback - Replace hardcoded emoji maps in run_agent.py, delegate_tool.py, and gateway/run.py with centralized get_tool_emoji() calls - Add 'tool_emojis' field to SkinConfig so skins can override per-tool emojis (e.g. ares skin could use swords instead of wrenches) - Add 11 tests (5 registry emoji, 6 display/skin integration) - Update AGENTS.md skin docs table Based on the approach from PR #1061 by ForgingAlex (emoji centralization in registry). This salvage fixes several issues from the original: - Does NOT split the cronjob tool (which would crash on missing schemas) - Does NOT change image_generate toolset/requires_env/is_async - Does NOT delete existing tests - Completes the centralization (gateway/run.py was missed) - Hooks into the skin system for full customizability --- AGENTS.md | 1 + agent/display.py | 26 +++++++ gateway/run.py | 41 +--------- hermes_cli/skin_engine.py | 8 ++ run_agent.py | 19 +---- tests/agent/test_display_emoji.py | 123 ++++++++++++++++++++++++++++++ tests/tools/test_registry.py | 42 ++++++++++ tools/browser_tool.py | 11 +++ tools/clarify_tool.py | 1 + tools/code_execution_tool.py | 1 + tools/cronjob_tools.py | 1 + tools/delegate_tool.py | 12 +-- tools/file_tools.py | 8 +- tools/homeassistant_tool.py | 4 + tools/honcho_tools.py | 4 + tools/image_generation_tool.py | 1 + tools/memory_tool.py | 1 + tools/mixture_of_agents_tool.py | 1 + tools/process_registry.py | 1 + tools/registry.py | 12 ++- tools/rl_training_tool.py | 20 ++--- tools/send_message_tool.py | 1 + tools/session_search_tool.py | 1 + tools/skill_manager_tool.py | 1 + tools/skills_tool.py | 2 + tools/terminal_tool.py | 1 + tools/todo_tool.py | 1 + tools/tts_tool.py | 1 + tools/vision_tools.py | 1 + tools/web_tools.py | 2 + 30 files changed, 268 insertions(+), 81 deletions(-) create mode 100644 tests/agent/test_display_emoji.py diff --git a/AGENTS.md b/AGENTS.md index 6f58cbd1..5c31f1d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -235,6 +235,7 @@ hermes_cli/skin_engine.py # SkinConfig dataclass, built-in skins, YAML loader | Spinner verbs | `spinner.thinking_verbs` | `display.py` | | Spinner wings (optional) | `spinner.wings` | `display.py` | | Tool output prefix | `tool_prefix` | `display.py` | +| Per-tool emojis | `tool_emojis` | `display.py` → `get_tool_emoji()` | | Agent name | `branding.agent_name` | `banner.py`, `cli.py` | | Welcome message | `branding.welcome` | `cli.py` | | Response box label | `branding.response_label` | `cli.py` | diff --git a/agent/display.py b/agent/display.py index faec5a42..c114db0b 100644 --- a/agent/display.py +++ b/agent/display.py @@ -59,6 +59,32 @@ def get_skin_tool_prefix() -> str: return "┊" +def get_tool_emoji(tool_name: str, default: str = "⚡") -> str: + """Get the display emoji for a tool. + + Resolution order: + 1. Active skin's ``tool_emojis`` overrides (if a skin is loaded) + 2. Tool registry's per-tool ``emoji`` field + 3. *default* fallback + """ + # 1. Skin override + skin = _get_skin() + if skin and skin.tool_emojis: + override = skin.tool_emojis.get(tool_name) + if override: + return override + # 2. Registry default + try: + from tools.registry import registry + emoji = registry.get_emoji(tool_name, default="") + if emoji: + return emoji + except Exception: + pass + # 3. Hardcoded fallback + return default + + # ========================================================================= # Tool preview (one-line summary of a tool call's primary argument) # ========================================================================= diff --git a/gateway/run.py b/gateway/run.py index e7bfb625..5b7ca791 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -3856,45 +3856,8 @@ class GatewayRunner: last_tool[0] = tool_name # Build progress message with primary argument preview - tool_emojis = { - "terminal": "💻", - "process": "⚙️", - "web_search": "🔍", - "web_extract": "📄", - "read_file": "📖", - "write_file": "✍️", - "patch": "🔧", - "search": "🔎", - "search_files": "🔎", - "list_directory": "📂", - "image_generate": "🎨", - "text_to_speech": "🔊", - "browser_navigate": "🌐", - "browser_click": "👆", - "browser_type": "⌨️", - "browser_snapshot": "📸", - "browser_scroll": "📜", - "browser_back": "◀️", - "browser_press": "⌨️", - "browser_close": "🚪", - "browser_get_images": "🖼️", - "browser_vision": "👁️", - "moa_query": "🧠", - "mixture_of_agents": "🧠", - "vision_analyze": "👁️", - "skill_view": "📚", - "skills_list": "📋", - "todo": "📋", - "memory": "🧠", - "session_search": "🔍", - "send_message": "📨", - "cronjob": "⏰", - "execute_code": "🐍", - "delegate_task": "🔀", - "clarify": "❓", - "skill_manage": "📝", - } - emoji = tool_emojis.get(tool_name, "⚙️") + from agent.display import get_tool_emoji + emoji = get_tool_emoji(tool_name, default="⚙️") # Verbose mode: show detailed arguments if progress_mode == "verbose" and args: diff --git a/hermes_cli/skin_engine.py b/hermes_cli/skin_engine.py index e73e73de..dad666ba 100644 --- a/hermes_cli/skin_engine.py +++ b/hermes_cli/skin_engine.py @@ -60,6 +60,12 @@ All fields are optional. Missing values inherit from the ``default`` skin. # Tool prefix: character for tool output lines (default: ┊) tool_prefix: "┊" + # Tool emojis: override the default emoji for any tool (used in spinners & progress) + tool_emojis: + terminal: "⚔" # Override terminal tool emoji + web_search: "🔮" # Override web_search tool emoji + # Any tool not listed here uses its registry default + USAGE ===== @@ -111,6 +117,7 @@ class SkinConfig: spinner: Dict[str, Any] = field(default_factory=dict) branding: Dict[str, str] = field(default_factory=dict) tool_prefix: str = "┊" + tool_emojis: Dict[str, str] = field(default_factory=dict) # per-tool emoji overrides banner_logo: str = "" # Rich-markup ASCII art logo (replaces HERMES_AGENT_LOGO) banner_hero: str = "" # Rich-markup hero art (replaces HERMES_CADUCEUS) @@ -541,6 +548,7 @@ def _build_skin_config(data: Dict[str, Any]) -> SkinConfig: spinner=spinner, branding=branding, tool_prefix=data.get("tool_prefix", default.get("tool_prefix", "┊")), + tool_emojis=data.get("tool_emojis", {}), banner_logo=data.get("banner_logo", ""), banner_hero=data.get("banner_hero", ""), ) diff --git a/run_agent.py b/run_agent.py index 29f01120..101f8c8c 100644 --- a/run_agent.py +++ b/run_agent.py @@ -90,6 +90,7 @@ from agent.display import ( KawaiiSpinner, build_tool_preview as _build_tool_preview, get_cute_tool_message as _get_cute_tool_message_impl, _detect_tool_failure, + get_tool_emoji as _get_tool_emoji, ) from agent.trajectory import ( convert_scratchpad_to_think, has_incomplete_scratchpad, @@ -4085,23 +4086,7 @@ class AIAgent: self._vprint(f" {cute_msg}") elif self.quiet_mode and self._stream_callback is None: face = random.choice(KawaiiSpinner.KAWAII_WAITING) - tool_emoji_map = { - 'web_search': '🔍', 'web_extract': '📄', 'web_crawl': '🕸️', - 'terminal': '💻', 'process': '⚙️', - 'read_file': '📖', 'write_file': '✍️', 'patch': '🔧', 'search_files': '🔎', - 'browser_navigate': '🌐', 'browser_snapshot': '📸', - 'browser_click': '👆', 'browser_type': '⌨️', - 'browser_scroll': '📜', 'browser_back': '◀️', - 'browser_press': '⌨️', 'browser_close': '🚪', - 'browser_get_images': '🖼️', 'browser_vision': '👁️', - 'image_generate': '🎨', 'text_to_speech': '🔊', - 'vision_analyze': '👁️', 'mixture_of_agents': '🧠', - 'skills_list': '📚', 'skill_view': '📚', - 'cronjob': '⏰', - 'send_message': '📨', 'todo': '📋', 'memory': '🧠', 'session_search': '🔍', - 'clarify': '❓', 'execute_code': '🐍', 'delegate_task': '🔀', - } - emoji = tool_emoji_map.get(function_name, '⚡') + emoji = _get_tool_emoji(function_name) preview = _build_tool_preview(function_name, function_args) or function_name if len(preview) > 30: preview = preview[:27] + "..." diff --git a/tests/agent/test_display_emoji.py b/tests/agent/test_display_emoji.py new file mode 100644 index 00000000..a48cfe9c --- /dev/null +++ b/tests/agent/test_display_emoji.py @@ -0,0 +1,123 @@ +"""Tests for get_tool_emoji in agent/display.py — skin + registry integration.""" + +from unittest.mock import patch as mock_patch, MagicMock + +from agent.display import get_tool_emoji + + +class TestGetToolEmoji: + """Verify the skin → registry → fallback resolution chain.""" + + def test_returns_registry_emoji_when_no_skin(self): + """Registry-registered emoji is used when no skin is active.""" + mock_registry = MagicMock() + mock_registry.get_emoji.return_value = "🎨" + with mock_patch("agent.display._get_skin", return_value=None), \ + mock_patch("agent.display.registry", mock_registry, create=True): + # Need to patch the import inside get_tool_emoji + pass + # Direct test: patch the lazy import path + with mock_patch("agent.display._get_skin", return_value=None): + # get_tool_emoji will try to import registry — mock that + mock_reg = MagicMock() + mock_reg.get_emoji.return_value = "📖" + with mock_patch.dict("sys.modules", {}): + import sys + # Patch tools.registry module + mock_module = MagicMock() + mock_module.registry = mock_reg + with mock_patch.dict(sys.modules, {"tools.registry": mock_module}): + result = get_tool_emoji("read_file") + assert result == "📖" + + def test_skin_override_takes_precedence(self): + """Skin tool_emojis override registry defaults.""" + skin = MagicMock() + skin.tool_emojis = {"terminal": "⚔"} + with mock_patch("agent.display._get_skin", return_value=skin): + result = get_tool_emoji("terminal") + assert result == "⚔" + + def test_skin_empty_dict_falls_through(self): + """Empty skin tool_emojis falls through to registry.""" + skin = MagicMock() + skin.tool_emojis = {} + mock_reg = MagicMock() + mock_reg.get_emoji.return_value = "💻" + import sys + mock_module = MagicMock() + mock_module.registry = mock_reg + with mock_patch("agent.display._get_skin", return_value=skin), \ + mock_patch.dict(sys.modules, {"tools.registry": mock_module}): + result = get_tool_emoji("terminal") + assert result == "💻" + + def test_fallback_default(self): + """When neither skin nor registry has an emoji, use the default.""" + skin = MagicMock() + skin.tool_emojis = {} + mock_reg = MagicMock() + mock_reg.get_emoji.return_value = "" + import sys + mock_module = MagicMock() + mock_module.registry = mock_reg + with mock_patch("agent.display._get_skin", return_value=skin), \ + mock_patch.dict(sys.modules, {"tools.registry": mock_module}): + result = get_tool_emoji("unknown_tool") + assert result == "⚡" + + def test_custom_default(self): + """Custom default is returned when nothing matches.""" + with mock_patch("agent.display._get_skin", return_value=None): + mock_reg = MagicMock() + mock_reg.get_emoji.return_value = "" + import sys + mock_module = MagicMock() + mock_module.registry = mock_reg + with mock_patch.dict(sys.modules, {"tools.registry": mock_module}): + result = get_tool_emoji("x", default="⚙️") + assert result == "⚙️" + + def test_skin_override_only_for_matching_tool(self): + """Skin override for one tool doesn't affect others.""" + skin = MagicMock() + skin.tool_emojis = {"terminal": "⚔"} + mock_reg = MagicMock() + mock_reg.get_emoji.return_value = "🔍" + import sys + mock_module = MagicMock() + mock_module.registry = mock_reg + with mock_patch("agent.display._get_skin", return_value=skin), \ + mock_patch.dict(sys.modules, {"tools.registry": mock_module}): + assert get_tool_emoji("terminal") == "⚔" # skin override + assert get_tool_emoji("web_search") == "🔍" # registry fallback + + +class TestSkinConfigToolEmojis: + """Verify SkinConfig handles tool_emojis field correctly.""" + + def test_skin_config_has_tool_emojis_field(self): + from hermes_cli.skin_engine import SkinConfig + skin = SkinConfig(name="test") + assert skin.tool_emojis == {} + + def test_skin_config_accepts_tool_emojis(self): + from hermes_cli.skin_engine import SkinConfig + emojis = {"terminal": "⚔", "web_search": "🔮"} + skin = SkinConfig(name="test", tool_emojis=emojis) + assert skin.tool_emojis == emojis + + def test_build_skin_config_includes_tool_emojis(self): + from hermes_cli.skin_engine import _build_skin_config + data = { + "name": "custom", + "tool_emojis": {"terminal": "🗡️", "patch": "⚒️"}, + } + skin = _build_skin_config(data) + assert skin.tool_emojis == {"terminal": "🗡️", "patch": "⚒️"} + + def test_build_skin_config_empty_tool_emojis_default(self): + from hermes_cli.skin_engine import _build_skin_config + data = {"name": "minimal"} + skin = _build_skin_config(data) + assert skin.tool_emojis == {} diff --git a/tests/tools/test_registry.py b/tests/tools/test_registry.py index de20f52b..eac4ab04 100644 --- a/tests/tools/test_registry.py +++ b/tests/tools/test_registry.py @@ -232,6 +232,48 @@ class TestCheckFnExceptionHandling: assert any(u["name"] == "crashes" for u in unavailable) +class TestEmojiMetadata: + """Verify per-tool emoji registration and lookup.""" + + def test_emoji_stored_on_entry(self): + reg = ToolRegistry() + reg.register( + name="t", toolset="s", schema=_make_schema(), + handler=_dummy_handler, emoji="🔥", + ) + assert reg._tools["t"].emoji == "🔥" + + def test_get_emoji_returns_registered(self): + reg = ToolRegistry() + reg.register( + name="t", toolset="s", schema=_make_schema(), + handler=_dummy_handler, emoji="🎯", + ) + assert reg.get_emoji("t") == "🎯" + + def test_get_emoji_returns_default_when_unset(self): + reg = ToolRegistry() + reg.register( + name="t", toolset="s", schema=_make_schema(), + handler=_dummy_handler, + ) + assert reg.get_emoji("t") == "⚡" + assert reg.get_emoji("t", default="🔧") == "🔧" + + def test_get_emoji_returns_default_for_unknown_tool(self): + reg = ToolRegistry() + assert reg.get_emoji("nonexistent") == "⚡" + assert reg.get_emoji("nonexistent", default="❓") == "❓" + + def test_emoji_empty_string_treated_as_unset(self): + reg = ToolRegistry() + reg.register( + name="t", toolset="s", schema=_make_schema(), + handler=_dummy_handler, emoji="", + ) + assert reg.get_emoji("t") == "⚡" + + class TestSecretCaptureResultContract: def test_secret_request_result_does_not_include_secret_value(self): result = { diff --git a/tools/browser_tool.py b/tools/browser_tool.py index ecdff753..88eba388 100644 --- a/tools/browser_tool.py +++ b/tools/browser_tool.py @@ -1833,6 +1833,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_navigate"], handler=lambda args, **kw: browser_navigate(url=args.get("url", ""), task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="🌐", ) registry.register( name="browser_snapshot", @@ -1841,6 +1842,7 @@ registry.register( handler=lambda args, **kw: browser_snapshot( full=args.get("full", False), task_id=kw.get("task_id"), user_task=kw.get("user_task")), check_fn=check_browser_requirements, + emoji="📸", ) registry.register( name="browser_click", @@ -1848,6 +1850,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_click"], handler=lambda args, **kw: browser_click(**args, task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="👆", ) registry.register( name="browser_type", @@ -1855,6 +1858,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_type"], handler=lambda args, **kw: browser_type(**args, task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="⌨️", ) registry.register( name="browser_scroll", @@ -1862,6 +1866,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_scroll"], handler=lambda args, **kw: browser_scroll(**args, task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="📜", ) registry.register( name="browser_back", @@ -1869,6 +1874,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_back"], handler=lambda args, **kw: browser_back(task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="◀️", ) registry.register( name="browser_press", @@ -1876,6 +1882,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_press"], handler=lambda args, **kw: browser_press(key=args.get("key", ""), task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="⌨️", ) registry.register( name="browser_close", @@ -1883,6 +1890,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_close"], handler=lambda args, **kw: browser_close(task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="🚪", ) registry.register( name="browser_get_images", @@ -1890,6 +1898,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_get_images"], handler=lambda args, **kw: browser_get_images(task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="🖼️", ) registry.register( name="browser_vision", @@ -1897,6 +1906,7 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_vision"], handler=lambda args, **kw: browser_vision(question=args.get("question", ""), annotate=args.get("annotate", False), task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="👁️", ) registry.register( name="browser_console", @@ -1904,4 +1914,5 @@ registry.register( schema=_BROWSER_SCHEMA_MAP["browser_console"], handler=lambda args, **kw: browser_console(clear=args.get("clear", False), task_id=kw.get("task_id")), check_fn=check_browser_requirements, + emoji="🖥️", ) diff --git a/tools/clarify_tool.py b/tools/clarify_tool.py index e0552357..414e62a7 100644 --- a/tools/clarify_tool.py +++ b/tools/clarify_tool.py @@ -137,4 +137,5 @@ registry.register( choices=args.get("choices"), callback=kw.get("callback")), check_fn=check_clarify_requirements, + emoji="❓", ) diff --git a/tools/code_execution_tool.py b/tools/code_execution_tool.py index f25c983f..6c3f19b3 100644 --- a/tools/code_execution_tool.py +++ b/tools/code_execution_tool.py @@ -776,4 +776,5 @@ registry.register( task_id=kw.get("task_id"), enabled_tools=kw.get("enabled_tools")), check_fn=check_sandbox_requirements, + emoji="🐍", ) diff --git a/tools/cronjob_tools.py b/tools/cronjob_tools.py index 7a0daaf8..15971787 100644 --- a/tools/cronjob_tools.py +++ b/tools/cronjob_tools.py @@ -458,4 +458,5 @@ registry.register( task_id=kw.get("task_id"), ), check_fn=check_cronjob_requirements, + emoji="⏰", ) diff --git a/tools/delegate_tool.py b/tools/delegate_tool.py index 0d5908ab..1ac75ea8 100644 --- a/tools/delegate_tool.py +++ b/tools/delegate_tool.py @@ -116,15 +116,8 @@ def _build_child_progress_callback(task_index: int, parent_agent, task_count: in # Regular tool call event if spinner: short = (preview[:35] + "...") if preview and len(preview) > 35 else (preview or "") - tool_emojis = { - "terminal": "💻", "web_search": "🔍", "web_extract": "📄", - "read_file": "📖", "write_file": "✍️", "patch": "🔧", - "search_files": "🔎", "list_directory": "📂", - "browser_navigate": "🌐", "browser_click": "👆", - "text_to_speech": "🔊", "image_generate": "🎨", - "vision_analyze": "👁️", "process": "⚙️", - } - emoji = tool_emojis.get(tool_name, "⚡") + from agent.display import get_tool_emoji + emoji = get_tool_emoji(tool_name) line = f" {prefix}├─ {emoji} {tool_name}" if short: line += f" \"{short}\"" @@ -758,4 +751,5 @@ registry.register( max_iterations=args.get("max_iterations"), parent_agent=kw.get("parent_agent")), check_fn=check_delegate_requirements, + emoji="🔀", ) diff --git a/tools/file_tools.py b/tools/file_tools.py index e2535b06..c2ed6ec5 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -464,7 +464,7 @@ def _handle_search_files(args, **kw): output_mode=args.get("output_mode", "content"), context=args.get("context", 0), task_id=tid) -registry.register(name="read_file", toolset="file", schema=READ_FILE_SCHEMA, handler=_handle_read_file, check_fn=_check_file_reqs) -registry.register(name="write_file", toolset="file", schema=WRITE_FILE_SCHEMA, handler=_handle_write_file, check_fn=_check_file_reqs) -registry.register(name="patch", toolset="file", schema=PATCH_SCHEMA, handler=_handle_patch, check_fn=_check_file_reqs) -registry.register(name="search_files", toolset="file", schema=SEARCH_FILES_SCHEMA, handler=_handle_search_files, check_fn=_check_file_reqs) +registry.register(name="read_file", toolset="file", schema=READ_FILE_SCHEMA, handler=_handle_read_file, check_fn=_check_file_reqs, emoji="📖") +registry.register(name="write_file", toolset="file", schema=WRITE_FILE_SCHEMA, handler=_handle_write_file, check_fn=_check_file_reqs, emoji="✍️") +registry.register(name="patch", toolset="file", schema=PATCH_SCHEMA, handler=_handle_patch, check_fn=_check_file_reqs, emoji="🔧") +registry.register(name="search_files", toolset="file", schema=SEARCH_FILES_SCHEMA, handler=_handle_search_files, check_fn=_check_file_reqs, emoji="🔎") diff --git a/tools/homeassistant_tool.py b/tools/homeassistant_tool.py index a9077cff..62125a7f 100644 --- a/tools/homeassistant_tool.py +++ b/tools/homeassistant_tool.py @@ -459,6 +459,7 @@ registry.register( schema=HA_LIST_ENTITIES_SCHEMA, handler=_handle_list_entities, check_fn=_check_ha_available, + emoji="🏠", ) registry.register( @@ -467,6 +468,7 @@ registry.register( schema=HA_GET_STATE_SCHEMA, handler=_handle_get_state, check_fn=_check_ha_available, + emoji="🏠", ) registry.register( @@ -475,6 +477,7 @@ registry.register( schema=HA_LIST_SERVICES_SCHEMA, handler=_handle_list_services, check_fn=_check_ha_available, + emoji="🏠", ) registry.register( @@ -483,4 +486,5 @@ registry.register( schema=HA_CALL_SERVICE_SCHEMA, handler=_handle_call_service, check_fn=_check_ha_available, + emoji="🏠", ) diff --git a/tools/honcho_tools.py b/tools/honcho_tools.py index 7d5aec5b..6ee8ad65 100644 --- a/tools/honcho_tools.py +++ b/tools/honcho_tools.py @@ -222,6 +222,7 @@ registry.register( schema=_PROFILE_SCHEMA, handler=_handle_honcho_profile, check_fn=_check_honcho_available, + emoji="🔮", ) registry.register( @@ -230,6 +231,7 @@ registry.register( schema=_SEARCH_SCHEMA, handler=_handle_honcho_search, check_fn=_check_honcho_available, + emoji="🔮", ) registry.register( @@ -238,6 +240,7 @@ registry.register( schema=_QUERY_SCHEMA, handler=_handle_honcho_context, check_fn=_check_honcho_available, + emoji="🔮", ) registry.register( @@ -246,4 +249,5 @@ registry.register( schema=_CONCLUDE_SCHEMA, handler=_handle_honcho_conclude, check_fn=_check_honcho_available, + emoji="🔮", ) diff --git a/tools/image_generation_tool.py b/tools/image_generation_tool.py index 00cc5912..440a1236 100644 --- a/tools/image_generation_tool.py +++ b/tools/image_generation_tool.py @@ -558,4 +558,5 @@ registry.register( check_fn=check_image_generation_requirements, requires_env=["FAL_KEY"], is_async=False, # Switched to sync fal_client API to fix "Event loop is closed" in gateway + emoji="🎨", ) diff --git a/tools/memory_tool.py b/tools/memory_tool.py index b921a84f..f77e8116 100644 --- a/tools/memory_tool.py +++ b/tools/memory_tool.py @@ -496,6 +496,7 @@ registry.register( old_text=args.get("old_text"), store=kw.get("store")), check_fn=check_memory_requirements, + emoji="🧠", ) diff --git a/tools/mixture_of_agents_tool.py b/tools/mixture_of_agents_tool.py index d23297d5..d62cfa81 100644 --- a/tools/mixture_of_agents_tool.py +++ b/tools/mixture_of_agents_tool.py @@ -544,4 +544,5 @@ registry.register( check_fn=check_moa_requirements, requires_env=["OPENROUTER_API_KEY"], is_async=True, + emoji="🧠", ) diff --git a/tools/process_registry.py b/tools/process_registry.py index 51c42453..ceb45ab2 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -858,4 +858,5 @@ registry.register( toolset="terminal", schema=PROCESS_SCHEMA, handler=_handle_process, + emoji="⚙️", ) diff --git a/tools/registry.py b/tools/registry.py index b56cb64c..513638a7 100644 --- a/tools/registry.py +++ b/tools/registry.py @@ -26,11 +26,11 @@ class ToolEntry: __slots__ = ( "name", "toolset", "schema", "handler", "check_fn", - "requires_env", "is_async", "description", + "requires_env", "is_async", "description", "emoji", ) def __init__(self, name, toolset, schema, handler, check_fn, - requires_env, is_async, description): + requires_env, is_async, description, emoji): self.name = name self.toolset = toolset self.schema = schema @@ -39,6 +39,7 @@ class ToolEntry: self.requires_env = requires_env self.is_async = is_async self.description = description + self.emoji = emoji class ToolRegistry: @@ -62,6 +63,7 @@ class ToolRegistry: requires_env: list = None, is_async: bool = False, description: str = "", + emoji: str = "", ): """Register a tool. Called at module-import time by each tool file.""" self._tools[name] = ToolEntry( @@ -73,6 +75,7 @@ class ToolRegistry: requires_env=requires_env or [], is_async=is_async, description=description or schema.get("description", ""), + emoji=emoji, ) if check_fn and toolset not in self._toolset_checks: self._toolset_checks[toolset] = check_fn @@ -141,6 +144,11 @@ class ToolRegistry: entry = self._tools.get(name) return entry.toolset if entry else None + def get_emoji(self, name: str, default: str = "⚡") -> str: + """Return the emoji for a tool, or *default* if unset.""" + entry = self._tools.get(name) + return (entry.emoji if entry and entry.emoji else default) + def get_tool_to_toolset_map(self) -> Dict[str, str]: """Return ``{tool_name: toolset_name}`` for every registered tool.""" return {name: e.toolset for name, e in self._tools.items()} diff --git a/tools/rl_training_tool.py b/tools/rl_training_tool.py index a1948e21..6d32bd53 100644 --- a/tools/rl_training_tool.py +++ b/tools/rl_training_tool.py @@ -1374,24 +1374,24 @@ RL_TEST_INFERENCE_SCHEMA = {"name": "rl_test_inference", "description": "Quick i _rl_env = ["TINKER_API_KEY", "WANDB_API_KEY"] -registry.register(name="rl_list_environments", toolset="rl", schema=RL_LIST_ENVIRONMENTS_SCHEMA, +registry.register(name="rl_list_environments", emoji="🧪", toolset="rl", schema=RL_LIST_ENVIRONMENTS_SCHEMA, handler=lambda args, **kw: rl_list_environments(), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_select_environment", toolset="rl", schema=RL_SELECT_ENVIRONMENT_SCHEMA, +registry.register(name="rl_select_environment", emoji="🧪", toolset="rl", schema=RL_SELECT_ENVIRONMENT_SCHEMA, handler=lambda args, **kw: rl_select_environment(name=args.get("name", "")), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_get_current_config", toolset="rl", schema=RL_GET_CURRENT_CONFIG_SCHEMA, +registry.register(name="rl_get_current_config", emoji="🧪", toolset="rl", schema=RL_GET_CURRENT_CONFIG_SCHEMA, handler=lambda args, **kw: rl_get_current_config(), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_edit_config", toolset="rl", schema=RL_EDIT_CONFIG_SCHEMA, +registry.register(name="rl_edit_config", emoji="🧪", toolset="rl", schema=RL_EDIT_CONFIG_SCHEMA, handler=lambda args, **kw: rl_edit_config(field=args.get("field", ""), value=args.get("value")), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_start_training", toolset="rl", schema=RL_START_TRAINING_SCHEMA, +registry.register(name="rl_start_training", emoji="🧪", toolset="rl", schema=RL_START_TRAINING_SCHEMA, handler=lambda args, **kw: rl_start_training(), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_check_status", toolset="rl", schema=RL_CHECK_STATUS_SCHEMA, +registry.register(name="rl_check_status", emoji="🧪", toolset="rl", schema=RL_CHECK_STATUS_SCHEMA, handler=lambda args, **kw: rl_check_status(run_id=args.get("run_id", "")), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_stop_training", toolset="rl", schema=RL_STOP_TRAINING_SCHEMA, +registry.register(name="rl_stop_training", emoji="🧪", toolset="rl", schema=RL_STOP_TRAINING_SCHEMA, handler=lambda args, **kw: rl_stop_training(run_id=args.get("run_id", "")), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_get_results", toolset="rl", schema=RL_GET_RESULTS_SCHEMA, +registry.register(name="rl_get_results", emoji="🧪", toolset="rl", schema=RL_GET_RESULTS_SCHEMA, handler=lambda args, **kw: rl_get_results(run_id=args.get("run_id", "")), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_list_runs", toolset="rl", schema=RL_LIST_RUNS_SCHEMA, +registry.register(name="rl_list_runs", emoji="🧪", toolset="rl", schema=RL_LIST_RUNS_SCHEMA, handler=lambda args, **kw: rl_list_runs(), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) -registry.register(name="rl_test_inference", toolset="rl", schema=RL_TEST_INFERENCE_SCHEMA, +registry.register(name="rl_test_inference", emoji="🧪", toolset="rl", schema=RL_TEST_INFERENCE_SCHEMA, handler=lambda args, **kw: rl_test_inference(num_steps=args.get("num_steps", 3), group_size=args.get("group_size", 16), models=args.get("models")), check_fn=check_rl_api_keys, requires_env=_rl_env, is_async=True) diff --git a/tools/send_message_tool.py b/tools/send_message_tool.py index 6a7260fd..f7a87e76 100644 --- a/tools/send_message_tool.py +++ b/tools/send_message_tool.py @@ -512,4 +512,5 @@ registry.register( schema=SEND_MESSAGE_SCHEMA, handler=send_message_tool, check_fn=_check_send_message, + emoji="📨", ) diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index f4143fa1..8a8c1300 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -385,4 +385,5 @@ registry.register( db=kw.get("db"), current_session_id=kw.get("current_session_id")), check_fn=check_session_search_requirements, + emoji="🔍", ) diff --git a/tools/skill_manager_tool.py b/tools/skill_manager_tool.py index 6d0323bb..86d04e63 100644 --- a/tools/skill_manager_tool.py +++ b/tools/skill_manager_tool.py @@ -653,4 +653,5 @@ registry.register( old_string=args.get("old_string"), new_string=args.get("new_string"), replace_all=args.get("replace_all", False)), + emoji="📝", ) diff --git a/tools/skills_tool.py b/tools/skills_tool.py index 4186d628..bcde5d53 100644 --- a/tools/skills_tool.py +++ b/tools/skills_tool.py @@ -1261,6 +1261,7 @@ registry.register( category=args.get("category"), task_id=kw.get("task_id") ), check_fn=check_skills_requirements, + emoji="📚", ) registry.register( name="skill_view", @@ -1270,4 +1271,5 @@ registry.register( args.get("name", ""), file_path=args.get("file_path"), task_id=kw.get("task_id") ), check_fn=check_skills_requirements, + emoji="📚", ) diff --git a/tools/terminal_tool.py b/tools/terminal_tool.py index bf1d2b6b..778948d3 100644 --- a/tools/terminal_tool.py +++ b/tools/terminal_tool.py @@ -1317,4 +1317,5 @@ registry.register( schema=TERMINAL_SCHEMA, handler=_handle_terminal, check_fn=check_terminal_requirements, + emoji="💻", ) diff --git a/tools/todo_tool.py b/tools/todo_tool.py index 7b74d01e..b94e5474 100644 --- a/tools/todo_tool.py +++ b/tools/todo_tool.py @@ -264,4 +264,5 @@ registry.register( handler=lambda args, **kw: todo_tool( todos=args.get("todos"), merge=args.get("merge", False), store=kw.get("store")), check_fn=check_todo_requirements, + emoji="📋", ) diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 286bb14b..e00771f2 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -743,4 +743,5 @@ registry.register( text=args.get("text", ""), output_path=args.get("output_path")), check_fn=check_tts_requirements, + emoji="🔊", ) diff --git a/tools/vision_tools.py b/tools/vision_tools.py index 954ffd28..a6d73a05 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -493,4 +493,5 @@ registry.register( handler=_handle_vision_analyze, check_fn=check_vision_requirements, is_async=True, + emoji="👁️", ) diff --git a/tools/web_tools.py b/tools/web_tools.py index 71a882a5..ede1adb0 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -1258,6 +1258,7 @@ registry.register( handler=lambda args, **kw: web_search_tool(args.get("query", ""), limit=5), check_fn=check_firecrawl_api_key, requires_env=["FIRECRAWL_API_KEY"], + emoji="🔍", ) registry.register( name="web_extract", @@ -1268,4 +1269,5 @@ registry.register( check_fn=check_firecrawl_api_key, requires_env=["FIRECRAWL_API_KEY"], is_async=True, + emoji="📄", )