fix: enforce strictly subtractive toolset filtration
Refactor tool resolution logic in model_tools.py to ensure that disabled_toolsets are always subtracted at the end, preventing composite toolsets (e.g. 'browser') from implicitly enabling tools that should be hidden. - Added 'disabled_toolsets' to DEFAULT_CONFIG in hermes_cli/config.py - Updated HermesCLI in cli.py to load and propagate disabled toolsets to AIAgent - Implemented robust two-phase resolution (additive then subtractive) in model_tools.py
This commit is contained in:
7
cli.py
7
cli.py
@@ -15,9 +15,8 @@ Usage:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
@@ -600,6 +599,7 @@ def load_cli_config() -> Dict[str, Any]:
|
|||||||
# Load configuration at module startup
|
# Load configuration at module startup
|
||||||
CLI_CONFIG = load_cli_config()
|
CLI_CONFIG = load_cli_config()
|
||||||
|
|
||||||
|
|
||||||
# Initialize centralized logging early — agent.log + errors.log in ~/.hermes/logs/.
|
# Initialize centralized logging early — agent.log + errors.log in ~/.hermes/logs/.
|
||||||
# This ensures CLI sessions produce a log trail even before AIAgent is instantiated.
|
# This ensures CLI sessions produce a log trail even before AIAgent is instantiated.
|
||||||
try:
|
try:
|
||||||
@@ -2118,6 +2118,8 @@ class HermesCLI:
|
|||||||
|
|
||||||
# Parse and validate toolsets
|
# Parse and validate toolsets
|
||||||
self.enabled_toolsets = toolsets
|
self.enabled_toolsets = toolsets
|
||||||
|
self.disabled_toolsets = CLI_CONFIG["agent"].get("disabled_toolsets") or []
|
||||||
|
|
||||||
if toolsets and "all" not in toolsets and "*" not in toolsets:
|
if toolsets and "all" not in toolsets and "*" not in toolsets:
|
||||||
# Validate each toolset — MCP server names are resolved via
|
# Validate each toolset — MCP server names are resolved via
|
||||||
# live registry aliases (registered during discover_mcp_tools),
|
# live registry aliases (registered during discover_mcp_tools),
|
||||||
@@ -3568,6 +3570,7 @@ class HermesCLI:
|
|||||||
credential_pool=runtime.get("credential_pool"),
|
credential_pool=runtime.get("credential_pool"),
|
||||||
max_iterations=self.max_turns,
|
max_iterations=self.max_turns,
|
||||||
enabled_toolsets=self.enabled_toolsets,
|
enabled_toolsets=self.enabled_toolsets,
|
||||||
|
disabled_toolsets=self.disabled_toolsets,
|
||||||
verbose_logging=self.verbose,
|
verbose_logging=self.verbose,
|
||||||
quiet_mode=not self.verbose,
|
quiet_mode=not self.verbose,
|
||||||
ephemeral_system_prompt=self.system_prompt if self.system_prompt else None,
|
ephemeral_system_prompt=self.system_prompt if self.system_prompt else None,
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ DEFAULT_CONFIG = {
|
|||||||
# remains available as a tool regardless of this setting — the routing
|
# remains available as a tool regardless of this setting — the routing
|
||||||
# only controls how inbound user images are presented.
|
# only controls how inbound user images are presented.
|
||||||
"image_input_mode": "auto",
|
"image_input_mode": "auto",
|
||||||
|
"disabled_toolsets": [],
|
||||||
},
|
},
|
||||||
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Public API (signatures preserved from the original 2,400-line version):
|
|||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
@@ -356,12 +358,17 @@ def _compute_tool_definitions(
|
|||||||
else:
|
else:
|
||||||
if not quiet_mode:
|
if not quiet_mode:
|
||||||
print(f"⚠️ Unknown toolset: {toolset_name}")
|
print(f"⚠️ Unknown toolset: {toolset_name}")
|
||||||
|
else:
|
||||||
elif disabled_toolsets:
|
# Default: start with everything
|
||||||
from toolsets import get_all_toolsets
|
from toolsets import get_all_toolsets
|
||||||
for ts_name in get_all_toolsets():
|
for ts_name in get_all_toolsets():
|
||||||
tools_to_include.update(resolve_toolset(ts_name))
|
tools_to_include.update(resolve_toolset(ts_name))
|
||||||
|
|
||||||
|
# Always apply disabled toolsets as a subtraction step at the end.
|
||||||
|
# This ensures that even if a composite toolset (like hermes-cli)
|
||||||
|
# is enabled, any tools belonging to a disabled toolset are strictly
|
||||||
|
# stripped out. See issue #15291.
|
||||||
|
if disabled_toolsets:
|
||||||
for toolset_name in disabled_toolsets:
|
for toolset_name in disabled_toolsets:
|
||||||
if validate_toolset(toolset_name):
|
if validate_toolset(toolset_name):
|
||||||
resolved = resolve_toolset(toolset_name)
|
resolved = resolve_toolset(toolset_name)
|
||||||
@@ -376,10 +383,6 @@ def _compute_tool_definitions(
|
|||||||
else:
|
else:
|
||||||
if not quiet_mode:
|
if not quiet_mode:
|
||||||
print(f"⚠️ Unknown toolset: {toolset_name}")
|
print(f"⚠️ Unknown toolset: {toolset_name}")
|
||||||
else:
|
|
||||||
from toolsets import get_all_toolsets
|
|
||||||
for ts_name in get_all_toolsets():
|
|
||||||
tools_to_include.update(resolve_toolset(ts_name))
|
|
||||||
|
|
||||||
# Plugin-registered tools are now resolved through the normal toolset
|
# Plugin-registered tools are now resolved through the normal toolset
|
||||||
# path — validate_toolset() / resolve_toolset() / get_all_toolsets()
|
# path — validate_toolset() / resolve_toolset() / get_all_toolsets()
|
||||||
|
|||||||
Reference in New Issue
Block a user