From 25c238205e90d5181e4b840d5266b9a76cf42f8d Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 11:19:54 -0700 Subject: [PATCH 01/18] fix: Allow both tool_paths and tools_paths in agent config Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index aa764bff223..c0a2bc4b967 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -124,7 +124,7 @@ def _get_agent_config(self): config["command_timeout"] = nested.getter(config, "command_timeout", 30) config["hot_reload"] = nested.getter(config, "hot_reload", False) - config["tools_paths"] = nested.getter(config, "tools_paths", []) + config["tool_paths"] = nested.getter(config, ["tool_paths", "tools_paths"], []) config["tools_includelist"] = nested.getter( config, ["tools_includelist", "tools_whitelist"], [] ) From 90602d621581b65812bfa0218c6edbbb0c6bbb29 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 12:08:10 -0700 Subject: [PATCH 02/18] refactor: Rename tool_paths to tools_paths in agent config --- cecli/coders/agent_coder.py | 3 +-- cecli/website/docs/config/agent-mode.md | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index c0a2bc4b967..1275884ba32 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -124,8 +124,7 @@ def _get_agent_config(self): config["command_timeout"] = nested.getter(config, "command_timeout", 30) config["hot_reload"] = nested.getter(config, "hot_reload", False) - config["tool_paths"] = nested.getter(config, ["tool_paths", "tools_paths"], []) - config["tools_includelist"] = nested.getter( +config["tool_paths"] = nested.getter(config, ["tools_paths", "tool_paths"], []) config["tools_includelist"] = nested.getter( config, ["tools_includelist", "tools_whitelist"], [] ) config["tools_excludelist"] = nested.getter( diff --git a/cecli/website/docs/config/agent-mode.md b/cecli/website/docs/config/agent-mode.md index f7a5e2e308a..54bbfc2348d 100644 --- a/cecli/website/docs/config/agent-mode.md +++ b/cecli/website/docs/config/agent-mode.md @@ -160,7 +160,7 @@ agent-config: # Tool configuration tools_includelist: [contextmanager", "replacetext", "finished"] # Optional: Whitelist of tools tools_excludelist: ["command", "commandinteractive"] # Optional: Blacklist of tools - tool_paths: ["./custom-tools", "~/my-tools"] # Optional: Directories or files containing custom tools + tools_paths: ["./custom-tools", "~/my-tools"] # Optional: Directories or files containing custom tools # Context blocks configuration include_context_blocks: ["todo_list", "git_status"] # Optional: Context blocks to include @@ -184,7 +184,7 @@ agent-config: - **`skip_cli_confirmations`**: YOLO mode, be brave and let the LLM cook, can also use the option `yolo` (default: False) - **`tools_includelist`**: Array of tool names to allow (only these tools will be available) - **`tools_excludelist`**: Array of tool names to exclude (these tools will be disabled) -- **`tool_paths`**: Array of directories or Python files containing custom tools to load +- **`tools_paths`**: Array of directories or Python files containing custom tools to load - **`include_context_blocks`**: Array of context block names to include (overrides default set) - **`exclude_context_blocks`**: Array of context block names to exclude from default set @@ -241,14 +241,14 @@ class Tool(BaseTool): return f"Tool executed with parameter: {parameter_name}" ``` -To load custom tools, specify the `tool_paths` configuration option in your agent config: +To load custom tools, specify the `tools_paths` configuration option in your agent config: ```yaml agent-config: tool_paths: ["./custom-tools", "~/my-tools"] ``` -The `tool_paths` can include: +The `tools_paths` can include: - **Directories**: All `.py` files in the directory will be scanned for `Tool` classes - **Individual Python files**: Specific tool files can be loaded directly From 369d6b4f387d6b0ca2ba9e56e20802c2029216ea Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 12:08:13 -0700 Subject: [PATCH 03/18] fix: Standardize tool_paths to tools_paths in agent config Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 3 ++- cecli/tools/utils/registry.py | 2 +- cecli/website/docs/config/agent-mode.md | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index 1275884ba32..aa764bff223 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -124,7 +124,8 @@ def _get_agent_config(self): config["command_timeout"] = nested.getter(config, "command_timeout", 30) config["hot_reload"] = nested.getter(config, "hot_reload", False) -config["tool_paths"] = nested.getter(config, ["tools_paths", "tool_paths"], []) config["tools_includelist"] = nested.getter( + config["tools_paths"] = nested.getter(config, "tools_paths", []) + config["tools_includelist"] = nested.getter( config, ["tools_includelist", "tools_whitelist"], [] ) config["tools_excludelist"] = nested.getter( diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index 24f852ddaa4..6540e92f476 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -52,7 +52,7 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: agent_config = {} # Load tools from tool_paths if specified - tool_paths = agent_config.get("tool_paths", []) + tool_paths = agent_config.get("tools_paths", []) for tool_path in tool_paths: path = Path(tool_path) diff --git a/cecli/website/docs/config/agent-mode.md b/cecli/website/docs/config/agent-mode.md index 54bbfc2348d..45ed44276e3 100644 --- a/cecli/website/docs/config/agent-mode.md +++ b/cecli/website/docs/config/agent-mode.md @@ -245,7 +245,7 @@ To load custom tools, specify the `tools_paths` configuration option in your age ```yaml agent-config: - tool_paths: ["./custom-tools", "~/my-tools"] + tools_paths: ["./custom-tools", "~/my-tools"] ``` The `tools_paths` can include: @@ -288,7 +288,7 @@ agent-config: # Tool configuration tools_includelist: ["contextmanager", "replacetext", "finished"] # Optional: Whitelist of tools tools_excludelist: ["command", "commandinteractive"] # Optional: Blacklist of tools - tool_paths: ["./custom-tools", "~/my-tools"] # Optional: Directories or files containing custom tools + tools_paths: ["./custom-tools", "~/my-tools"] # Optional: Directories or files containing custom tools # Context blocks configuration include_context_blocks: ["todo_list", "git_status"] # Optional: Context blocks to include From 316341de4011dc26677dd6f349397bc25a7b1149 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 13:54:01 -0700 Subject: [PATCH 04/18] feat: Announce loaded custom tools in TUI Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 4 ++++ cecli/tools/utils/registry.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index aa764bff223..ba7261e4905 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -43,6 +43,7 @@ class AgentCoder(Coder): def __init__(self, *args, **kwargs): self.recently_removed = {} self.tool_usage_history = [] + self.loaded_custom_tools = [] self.tool_usage_retries = 20 self.last_round_tools = [] self.tool_call_vectors = [] @@ -196,6 +197,9 @@ def _initialize_skills_manager(self, config): def show_announcements(self): super().show_announcements() + if self.loaded_custom_tools: + self.io.tool_output(f"Loaded custom tools: {', '.join(self.loaded_custom_tools)}") + skills = self.skills_manager.find_skills() if skills: skills_list = [] diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index 6540e92f476..a3e62cf0de0 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -37,7 +37,7 @@ def list_tools(cls) -> List[str]: return list(cls._tools.keys()) @classmethod - def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: + def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, Type], List[str]]: """ Build a filtered registry of tools based on agent configuration. @@ -46,13 +46,16 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: tools_includelist/tools_excludelist keys Returns: - Dictionary mapping normalized tool names to tool classes + A tuple containing: + - Dictionary mapping normalized tool names to tool classes + - List of names of custom tools that were loaded """ if agent_config is None: agent_config = {} # Load tools from tool_paths if specified tool_paths = agent_config.get("tools_paths", []) + loaded_custom_tools = [] for tool_path in tool_paths: path = Path(tool_path) @@ -65,6 +68,7 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: # Check if module has a Tool class if hasattr(module, "Tool"): cls.register(module.Tool) + loaded_custom_tools.append(module.Tool.NORM_NAME) except Exception as e: # Log error but continue with other files print(f"Error loading tool from {py_file}: {e}") @@ -75,6 +79,7 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: module = plugin_manager.load_module(str(path)) if hasattr(module, "Tool"): cls.register(module.Tool) + loaded_custom_tools.append(module.Tool.NORM_NAME) except Exception as e: print(f"Error loading tool from {path}: {e}") @@ -108,7 +113,7 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: # Store the built registry in the class attribute cls._registry = registry - return registry + return registry, loaded_custom_tools @classmethod def get_registered_tools(cls) -> List[str]: From 6c4a95d2493d0dfadd27a7fb389c9cd65324f61d Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 13:54:36 -0700 Subject: [PATCH 05/18] feat: Announce loaded custom tools in TUI Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index ba7261e4905..053751c253a 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -90,7 +90,9 @@ def __init__(self, *args, **kwargs): self.agent_finished = False self.agent_config = self._get_agent_config() self._setup_agent() - ToolRegistry.build_registry(agent_config=self.agent_config) + _, self.loaded_custom_tools = ToolRegistry.build_registry( + agent_config=self.agent_config + ) super().__init__(*args, **kwargs) def _setup_agent(self): From fcc0428c3ae3c7190c56ca5d89da77600ea100d6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 14:03:57 -0700 Subject: [PATCH 06/18] fix: Add checks for None or empty strings in plugin manager Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/helpers/plugin_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cecli/helpers/plugin_manager.py b/cecli/helpers/plugin_manager.py index f9e9bad4380..59f76adb93d 100644 --- a/cecli/helpers/plugin_manager.py +++ b/cecli/helpers/plugin_manager.py @@ -37,6 +37,8 @@ def normalize_filename(filename: str) -> str: :param filename: Original filename :return: Normalized module name """ + if not filename: + return "" # Remove extension name = Path(filename).stem @@ -58,6 +60,8 @@ def load_module(source, module_name=None, reload=False): :param module_name: name of module to register in sys.modules :return: loaded module """ + if not source: + return None # Convert to absolute path for cache key source_path = Path(source).resolve() From fe8887dcab3d38f2735b14bd89756663d963a9e7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Apr 2026 14:27:18 -0700 Subject: [PATCH 07/18] fix: Add check for NORM_NAME in tool registry Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/tools/utils/registry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index a3e62cf0de0..1d4551aeac9 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -68,7 +68,8 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, # Check if module has a Tool class if hasattr(module, "Tool"): cls.register(module.Tool) - loaded_custom_tools.append(module.Tool.NORM_NAME) + if module.Tool.NORM_NAME: + loaded_custom_tools.append(module.Tool.NORM_NAME) except Exception as e: # Log error but continue with other files print(f"Error loading tool from {py_file}: {e}") @@ -79,7 +80,8 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, module = plugin_manager.load_module(str(path)) if hasattr(module, "Tool"): cls.register(module.Tool) - loaded_custom_tools.append(module.Tool.NORM_NAME) + if module.Tool.NORM_NAME: + loaded_custom_tools.append(module.Tool.NORM_NAME) except Exception as e: print(f"Error loading tool from {path}: {e}") From 322349c87b65070ccd4fd680cdfb831b03fa065b Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Apr 2026 11:53:37 -0700 Subject: [PATCH 08/18] fix: Correctly call tool execute method in agent coder Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index 053751c253a..2743ba29af4 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -296,7 +296,7 @@ async def _execute_local_tool_calls(self, tool_calls_list): if norm_tool_name in ToolRegistry.get_registered_tools(): tool_module = ToolRegistry.get_tool(norm_tool_name) for params in parsed_args_list: - result = tool_module.process_response(self, params) + result = tool_module.execute(self, **params) if asyncio.iscoroutine(result): tasks.append(result) else: From 4169623cbc62d228793ed436e9efd1f55ca3fcb4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Apr 2026 12:05:08 -0700 Subject: [PATCH 09/18] fix: Correct local tool dispatch logic in AgentCoder Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index 2743ba29af4..9e20a719721 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -301,23 +301,8 @@ async def _execute_local_tool_calls(self, tool_calls_list): tasks.append(result) else: tasks.append(asyncio.to_thread(lambda: result)) - elif self.mcp_tools: - for server_name, server_tools in self.mcp_tools: - if any( - t.get("function", {}).get("name") == norm_tool_name - for t in server_tools - ): - server = self.mcp_manager.get_server(server_name) - if server: - for params in parsed_args_list: - tasks.append( - self._execute_mcp_tool(server, norm_tool_name, params) - ) - break - else: - all_results_content.append(f"Error: Unknown tool name '{tool_name}'") else: - all_results_content.append(f"Error: Unknown tool name '{tool_name}'") + all_results_content.append(f"Error: Unknown local tool name '{tool_name}'") if tasks: task_results = await asyncio.gather(*tasks) all_results_content.extend(str(res) for res in task_results) From 2c87290763d7e82c50ad9479f7381d10a56ad4e6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Apr 2026 12:42:31 -0700 Subject: [PATCH 10/18] fix: Improve error logging in tool registry and base coder Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/base_coder.py | 2 +- cecli/tools/utils/registry.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cecli/coders/base_coder.py b/cecli/coders/base_coder.py index 766208ee344..f88f9ae73f9 100755 --- a/cecli/coders/base_coder.py +++ b/cecli/coders/base_coder.py @@ -2587,7 +2587,7 @@ async def _execute_tool_calls(self, tool_calls): # Define the coroutine to execute all tool calls for a single server async def _exec_server_tools(server, tool_calls_list): - if isinstance(server, LocalServer): + if server.name == "Local": if hasattr(self, "_execute_local_tool_calls"): return await self._execute_local_tool_calls(tool_calls_list) else: diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index 1d4551aeac9..8286361520d 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -6,6 +6,7 @@ based on agent configuration. """ +import traceback from pathlib import Path from typing import Dict, List, Optional, Set, Type @@ -73,6 +74,7 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, except Exception as e: # Log error but continue with other files print(f"Error loading tool from {py_file}: {e}") + print(traceback.format_exc()) else: # If it's a file, try to load it directly if path.exists() and path.suffix == ".py": @@ -84,6 +86,7 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, loaded_custom_tools.append(module.Tool.NORM_NAME) except Exception as e: print(f"Error loading tool from {path}: {e}") + print(traceback.format_exc()) # Get include/exclude lists from config tools_includelist = agent_config.get( From 420a4f1a1156b8054cfb986bf42ca3fb30819db2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Apr 2026 13:07:37 -0700 Subject: [PATCH 11/18] fix: Use schema name for tool registration Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/tools/utils/registry.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index 8286361520d..2d4947d6fdb 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -23,8 +23,21 @@ class ToolRegistry: @classmethod def register(cls, tool_class): - """Register a tool class.""" - name = tool_class.NORM_NAME + """Register a tool class using the name from its SCHEMA.""" + name = None + if hasattr(tool_class, "SCHEMA"): + try: + name = tool_class.SCHEMA.get("function", {}).get("name", "").lower() + except Exception: + pass + + if not name and hasattr(tool_class, "NORM_NAME"): + name = tool_class.NORM_NAME + + if not name: + # Unable to determine a name, can't register + return + cls._tools[name] = tool_class @classmethod From 9980facc3c247329682597f0e81fd1e9d0b021ac Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Apr 2026 11:57:11 -0700 Subject: [PATCH 12/18] fix: Adjust tool registry tests for tuple return value Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- tests/tools/test_registry.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/tools/test_registry.py b/tests/tools/test_registry.py index b77f3d711ca..339689fd9ac 100644 --- a/tests/tools/test_registry.py +++ b/tests/tools/test_registry.py @@ -46,7 +46,7 @@ def test_get_tool(self): def test_build_registry_empty_config(self): """Test building registry with empty config""" - registry = ToolRegistry.build_registry({}) + registry, _ = ToolRegistry.build_registry({}) # Should include all tools (except possibly skill tools) assert len(registry) > 0, "Should return tools with empty config" @@ -58,20 +58,20 @@ def test_build_registry_empty_config(self): def test_build_registry_with_includelist(self): """Test filtering with tools_includelist""" - config = {"tools_includelist": ["contextmanager", "replacetext", "finished"]} - registry = ToolRegistry.build_registry(config) + config = {"tools_includelist": ["contextmanager", "replacetext"]} + registry, _ = ToolRegistry.build_registry(config) - # Should only include tools in the includelist - assert len(registry) == 3, "Should only include tools from includelist" + # Should only include tools from includelist, plus essential tools + assert len(registry) == 3, "Should include 2 from list + 1 essential" assert "contextmanager" in registry assert "replacetext" in registry - assert "finished" in registry + assert "finished" in registry # Essential assert "command" not in registry, "Should not include tools not in includelist" def test_build_registry_with_excludelist(self): """Test filtering with tools_excludelist""" config = {"tools_excludelist": ["command", "commandinteractive"]} - registry = ToolRegistry.build_registry(config) + registry, _ = ToolRegistry.build_registry(config) # Should exclude specified tools (except essentials) assert "command" not in registry, "Should exclude command" @@ -81,7 +81,7 @@ def test_build_registry_with_excludelist(self): def test_build_registry_exclude_essential(self): """Test that essential tools cannot be excluded""" config = {"tools_excludelist": ["contextmanager", "replacetext", "finished", "command"]} - registry = ToolRegistry.build_registry(config) + registry, _ = ToolRegistry.build_registry(config) # Essential tools should still be included despite excludelist assert "contextmanager" in registry, "Essential tool cannot be excluded" @@ -92,13 +92,13 @@ def test_build_registry_exclude_essential(self): def test_build_registry_combined_filters(self): """Test combined filtering with includelist and excludelist""" config = { - "tools_includelist": ["contextmanager", "replacetext", "finished", "command"], + "tools_includelist": ["contextmanager", "replacetext", "command"], "tools_excludelist": ["commandinteractive"], } - registry = ToolRegistry.build_registry(config) + registry, _ = ToolRegistry.build_registry(config) # Should respect all filters - assert len(registry) == 4, "Should include exactly 4 tools" + assert len(registry) == 4, "Should include exactly 4 tools (3 from list + finished)" assert "contextmanager" in registry assert "replacetext" in registry assert "finished" in registry @@ -125,7 +125,7 @@ def test_legacy_config_names(self): "tools_whitelist": ["contextmanager", "replacetext"], "tools_blacklist": ["command"], } - registry = ToolRegistry.build_registry(config) + registry, _ = ToolRegistry.build_registry(config) # Should work with legacy names assert "contextmanager" in registry @@ -140,7 +140,7 @@ def test_config_precedence(self): "tools_excludelist": ["commandinteractive"], "tools_blacklist": ["finished"], # Should be ignored for essential tool } - registry = ToolRegistry.build_registry(config) + registry, _ = ToolRegistry.build_registry(config) # New names should take precedence assert "contextmanager" in registry, "Should use tools_includelist" @@ -155,7 +155,7 @@ def test_registry_consistency(self): config = {"tools_includelist": ["contextmanager", "replacetext"]} # build_registry should return consistent results - registry = ToolRegistry.build_registry(config) + registry, _ = ToolRegistry.build_registry(config) filtered_names = ToolRegistry.get_registered_tools() assert set(registry.keys()) == set( From 3c132899a7259f8c3eb5e31ae0047d4164ce3095 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Apr 2026 12:15:20 -0700 Subject: [PATCH 13/18] fix: Resolve test failures in tool registry and repomap Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/base_coder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cecli/coders/base_coder.py b/cecli/coders/base_coder.py index f88f9ae73f9..a84107a5fe1 100755 --- a/cecli/coders/base_coder.py +++ b/cecli/coders/base_coder.py @@ -544,7 +544,7 @@ def __init__( max_code_line_length=map_max_line_length, repo_root=self.root, use_memory_cache=repomap_in_memory, - use_enhanced_map=False if not self.args or self.args.use_enhanced_map else True, + use_enhanced_map=getattr(self.args, "use_enhanced_map", False), ) self.summarizer = summarizer or ChatSummary( From 867fee4889f451099ea689eb27e2e354b2f31678 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Apr 2026 12:18:27 -0700 Subject: [PATCH 14/18] fix: Enable enhanced map for C# repo map test Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- tests/basic/test_repomap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/basic/test_repomap.py b/tests/basic/test_repomap.py index 0b6543c64e0..5ab7e56cf55 100644 --- a/tests/basic/test_repomap.py +++ b/tests/basic/test_repomap.py @@ -577,7 +577,7 @@ def _test_language_repo_map(self, lang, key, symbol): f.write(content) io = InputOutput() - repo_map = RepoMap(main_model=self.GPT35, io=io) + repo_map = RepoMap(main_model=self.GPT35, io=io, use_enhanced_map=True) other_files = [test_file] result = repo_map.get_repo_map([], other_files) dump(lang) From cb951e7f033a753de3a284dc189175d60a47cd66 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Apr 2026 14:04:04 -0700 Subject: [PATCH 15/18] fix: Adjust tool registry to return dict and fix local tool dispatch Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 3 ++- cecli/coders/base_coder.py | 2 +- cecli/tools/utils/registry.py | 11 ++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index 3f6cc5987f9..ebfcaf24513 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -90,9 +90,10 @@ def __init__(self, *args, **kwargs): self.agent_finished = False self.agent_config = self._get_agent_config() self._setup_agent() - _, self.loaded_custom_tools = ToolRegistry.build_registry( + ToolRegistry.build_registry( agent_config=self.agent_config ) + self.loaded_custom_tools = ToolRegistry.loaded_custom_tools super().__init__(*args, **kwargs) def _setup_agent(self): diff --git a/cecli/coders/base_coder.py b/cecli/coders/base_coder.py index 68fa5758422..9ffd632a063 100755 --- a/cecli/coders/base_coder.py +++ b/cecli/coders/base_coder.py @@ -2566,7 +2566,7 @@ async def _execute_tool_groups(self, tool_groups): # Execute tools for each server for server, tool_calls in tool_groups.items(): # Check if this server is an instance of LocalServer (local tools) - if isinstance(server, LocalServer): + if server.name == "Local": # Local tools - use _execute_local_tools local_responses = await self._execute_local_tools(tool_calls) all_responses[server] = local_responses diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index e4e15195eb1..dd458b501d4 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -20,6 +20,7 @@ class ToolRegistry: _tools: Dict[str, Type] = {} # normalized name -> Tool class _essential_tools: Set[str] = {"contextmanager", "replacetext", "finished"} _registry: Dict[str, Type] = {} # cached filtered registry + loaded_custom_tools: List[str] = [] @classmethod def register(cls, tool_class): @@ -51,7 +52,7 @@ def list_tools(cls) -> List[str]: return list(cls._tools.keys()) @classmethod - def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, Type], List[str]]: + def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: """ Build a filtered registry of tools based on agent configuration. @@ -60,9 +61,8 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, tools_includelist/tools_excludelist keys Returns: - A tuple containing: - - Dictionary mapping normalized tool names to tool classes - - List of names of custom tools that were loaded + A dictionary mapping normalized tool names to tool classes. + Custom loaded tools are stored in `ToolRegistry.loaded_custom_tools`. """ if agent_config is None: agent_config = {} @@ -131,7 +131,8 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> tuple[Dict[str, # Store the built registry in the class attribute cls._registry = registry - return registry, loaded_custom_tools + cls.loaded_custom_tools = loaded_custom_tools + return registry @classmethod def get_registered_tools(cls) -> List[str]: From 6b06813e8b5353505d548b55dd3cf12b506f8660 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Apr 2026 21:12:10 -0700 Subject: [PATCH 16/18] fix: Address tool registry logic and flake8 error Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- cecli/coders/agent_coder.py | 4 +--- cecli/tools/utils/registry.py | 38 +++++++++++++++++------------------ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/cecli/coders/agent_coder.py b/cecli/coders/agent_coder.py index ebfcaf24513..812ffe6754b 100644 --- a/cecli/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -90,9 +90,7 @@ def __init__(self, *args, **kwargs): self.agent_finished = False self.agent_config = self._get_agent_config() self._setup_agent() - ToolRegistry.build_registry( - agent_config=self.agent_config - ) + ToolRegistry.build_registry(agent_config=self.agent_config) self.loaded_custom_tools = ToolRegistry.loaded_custom_tools super().__init__(*args, **kwargs) diff --git a/cecli/tools/utils/registry.py b/cecli/tools/utils/registry.py index dd458b501d4..8f694334635 100644 --- a/cecli/tools/utils/registry.py +++ b/cecli/tools/utils/registry.py @@ -109,25 +109,25 @@ def build_registry(cls, agent_config: Optional[Dict] = None) -> Dict[str, Type]: "tools_excludelist", agent_config.get("tools_blacklist", []) ) - registry = {} - - for tool_name, tool_class in cls._tools.items(): - should_include = True - - # Apply include list if specified - if tools_includelist: - should_include = tool_name in tools_includelist - - # Essential tools are always included - if tool_name in cls._essential_tools: - should_include = True - - # Apply exclude list (unless essential) - if tool_name in tools_excludelist and tool_name not in cls._essential_tools: - should_include = False - - if should_include: - registry[tool_name] = tool_class + # Start with a base set of tools + if tools_includelist: + # If includelist is provided, start with only those tools + working_set = set(tools_includelist) + else: + # Otherwise, start with all registered tools + working_set = set(cls._tools.keys()) + + # Add essential tools, they can't be removed by the excludelist + working_set.update(cls._essential_tools) + + # Remove tools from the excludelist, but keep essential ones + if tools_excludelist: + for tool_name in tools_excludelist: + if tool_name in working_set and tool_name not in cls._essential_tools: + working_set.remove(tool_name) + + # Build the final registry from the working set + registry = {name: cls._tools[name] for name in working_set if name in cls._tools} # Store the built registry in the class attribute cls._registry = registry From e293426f50576443b708c00083f5d40490164f6c Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 4 Apr 2026 13:21:09 -0700 Subject: [PATCH 17/18] test --- cecli/coders/base_coder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cecli/coders/base_coder.py b/cecli/coders/base_coder.py index 9ffd632a063..b2a4f58e854 100755 --- a/cecli/coders/base_coder.py +++ b/cecli/coders/base_coder.py @@ -48,7 +48,6 @@ from cecli.io import ConfirmGroup, InputOutput from cecli.linter import Linter from cecli.llm import litellm -from cecli.mcp import LocalServer from cecli.models import RETRY_TIMEOUT from cecli.reasoning_tags import ( REASONING_TAG, From 1bb4064688ea63872d9c11a416872be270a4ffcd Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 4 Apr 2026 14:34:05 -0700 Subject: [PATCH 18/18] fix: Remove unused variable from ToolRegistry tests Co-authored-by: cecli (openai/gemini_cli/gemini-2.5-pro) --- tests/tools/test_registry.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/tools/test_registry.py b/tests/tools/test_registry.py index 339689fd9ac..ae124239c78 100644 --- a/tests/tools/test_registry.py +++ b/tests/tools/test_registry.py @@ -46,7 +46,7 @@ def test_get_tool(self): def test_build_registry_empty_config(self): """Test building registry with empty config""" - registry, _ = ToolRegistry.build_registry({}) + registry = ToolRegistry.build_registry({}) # Should include all tools (except possibly skill tools) assert len(registry) > 0, "Should return tools with empty config" @@ -59,7 +59,7 @@ def test_build_registry_empty_config(self): def test_build_registry_with_includelist(self): """Test filtering with tools_includelist""" config = {"tools_includelist": ["contextmanager", "replacetext"]} - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) # Should only include tools from includelist, plus essential tools assert len(registry) == 3, "Should include 2 from list + 1 essential" @@ -71,7 +71,7 @@ def test_build_registry_with_includelist(self): def test_build_registry_with_excludelist(self): """Test filtering with tools_excludelist""" config = {"tools_excludelist": ["command", "commandinteractive"]} - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) # Should exclude specified tools (except essentials) assert "command" not in registry, "Should exclude command" @@ -81,7 +81,7 @@ def test_build_registry_with_excludelist(self): def test_build_registry_exclude_essential(self): """Test that essential tools cannot be excluded""" config = {"tools_excludelist": ["contextmanager", "replacetext", "finished", "command"]} - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) # Essential tools should still be included despite excludelist assert "contextmanager" in registry, "Essential tool cannot be excluded" @@ -95,7 +95,7 @@ def test_build_registry_combined_filters(self): "tools_includelist": ["contextmanager", "replacetext", "command"], "tools_excludelist": ["commandinteractive"], } - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) # Should respect all filters assert len(registry) == 4, "Should include exactly 4 tools (3 from list + finished)" @@ -125,7 +125,7 @@ def test_legacy_config_names(self): "tools_whitelist": ["contextmanager", "replacetext"], "tools_blacklist": ["command"], } - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) # Should work with legacy names assert "contextmanager" in registry @@ -140,7 +140,7 @@ def test_config_precedence(self): "tools_excludelist": ["commandinteractive"], "tools_blacklist": ["finished"], # Should be ignored for essential tool } - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) # New names should take precedence assert "contextmanager" in registry, "Should use tools_includelist" @@ -155,7 +155,7 @@ def test_registry_consistency(self): config = {"tools_includelist": ["contextmanager", "replacetext"]} # build_registry should return consistent results - registry, _ = ToolRegistry.build_registry(config) + registry = ToolRegistry.build_registry(config) filtered_names = ToolRegistry.get_registered_tools() assert set(registry.keys()) == set(