diff --git a/agent/display.py b/agent/display.py index caa94b7d0..de47002d0 100644 --- a/agent/display.py +++ b/agent/display.py @@ -17,6 +17,23 @@ _RESET = "\033[0m" logger = logging.getLogger(__name__) +# ========================================================================= +# Configurable tool preview length (0 = no limit) +# Set once at startup by CLI or gateway from display.tool_preview_length config. +# ========================================================================= +_tool_preview_max_len: int = 0 # 0 = unlimited + + +def set_tool_preview_max_len(n: int) -> None: + """Set the global max length for tool call previews. 0 = no limit.""" + global _tool_preview_max_len + _tool_preview_max_len = max(int(n), 0) if n else 0 + + +def get_tool_preview_max_len() -> int: + """Return the configured max preview length (0 = unlimited).""" + return _tool_preview_max_len + # ========================================================================= # Skin-aware helpers (lazy import to avoid circular deps) @@ -94,8 +111,14 @@ def _oneline(text: str) -> str: return " ".join(text.split()) -def build_tool_preview(tool_name: str, args: dict, max_len: int = 40) -> str | None: - """Build a short preview of a tool call's primary argument for display.""" +def build_tool_preview(tool_name: str, args: dict, max_len: int | None = None) -> str | None: + """Build a short preview of a tool call's primary argument for display. + + *max_len* controls truncation. ``None`` (default) defers to the global + ``_tool_preview_max_len`` set via config; ``0`` means unlimited. + """ + if max_len is None: + max_len = _tool_preview_max_len if not args: return None primary_args = { @@ -190,7 +213,7 @@ def build_tool_preview(tool_name: str, args: dict, max_len: int = 40) -> str | N preview = _oneline(str(value)) if not preview: return None - if len(preview) > max_len: + if max_len > 0 and len(preview) > max_len: preview = preview[:max_len - 3] + "..." return preview @@ -484,10 +507,14 @@ def get_cute_tool_message( def _trunc(s, n=40): s = str(s) + if _tool_preview_max_len == 0: + return s # no limit return (s[:n-3] + "...") if len(s) > n else s def _path(p, n=35): p = str(p) + if _tool_preview_max_len == 0: + return p # no limit return ("..." + p[-(n-3):]) if len(p) > n else p def _wrap(line: str) -> str: diff --git a/cli.py b/cli.py index 4d8146bb6..5144b5bbb 100644 --- a/cli.py +++ b/cli.py @@ -449,6 +449,14 @@ try: except Exception: pass # Skin engine is optional — default skin used if unavailable +# Initialize tool preview length from config +try: + from agent.display import set_tool_preview_max_len + _tpl = CLI_CONFIG.get("display", {}).get("tool_preview_length", 0) + set_tool_preview_max_len(int(_tpl) if _tpl else 0) +except Exception: + pass + # Neuter AsyncHttpxClientWrapper.__del__ before any AsyncOpenAI clients are # created. The SDK's __del__ schedules aclose() on asyncio.get_running_loop() # which, during CLI idle time, finds prompt_toolkit's event loop and tries to @@ -4782,8 +4790,10 @@ class HermesCLI: from agent.display import get_tool_emoji emoji = get_tool_emoji(function_name) label = preview or function_name - if len(label) > 50: - label = label[:47] + "..." + from agent.display import get_tool_preview_max_len + _pl = get_tool_preview_max_len() + if _pl > 0 and len(label) > _pl: + label = label[:_pl - 3] + "..." self._spinner_text = f"{emoji} {label}" self._invalidate() diff --git a/gateway/run.py b/gateway/run.py index 07eaa84d1..d2f43a6fd 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -4941,6 +4941,14 @@ class GatewayRunner: from hermes_cli.tools_config import _get_platform_tools enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key)) + # Apply tool preview length config (0 = no limit) + try: + from agent.display import set_tool_preview_max_len + _tpl = user_config.get("display", {}).get("tool_preview_length", 0) + set_tool_preview_max_len(int(_tpl) if _tpl else 0) + except Exception: + pass + # Tool progress mode from config.yaml: "all", "new", "verbose", "off" # Falls back to env vars for backward compatibility. # YAML 1.1 parses bare `off` as boolean False — normalise before @@ -4986,9 +4994,11 @@ class GatewayRunner: return if preview: - # Truncate preview to keep messages clean - if len(preview) > 80: - preview = preview[:77] + "..." + # Truncate preview unless config says unlimited + from agent.display import get_tool_preview_max_len + _pl = get_tool_preview_max_len() + if _pl > 0 and len(preview) > _pl: + preview = preview[:_pl - 3] + "..." msg = f"{emoji} {tool_name}: \"{preview}\"" else: msg = f"{emoji} {tool_name}..." diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 8a65d0e2c..34d4d1ac4 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -285,6 +285,7 @@ DEFAULT_CONFIG = { "show_cost": False, # Show $ cost in the status bar (off by default) "skin": "default", "tool_progress_command": False, # Enable /verbose command in messaging gateway + "tool_preview_length": 0, # Max chars for tool call previews (0 = no limit, show full paths/commands) }, # Privacy settings diff --git a/run_agent.py b/run_agent.py index 52c727f05..32661a1b7 100644 --- a/run_agent.py +++ b/run_agent.py @@ -5662,8 +5662,6 @@ class AIAgent: face = random.choice(KawaiiSpinner.KAWAII_WAITING) emoji = _get_tool_emoji(function_name) preview = _build_tool_preview(function_name, function_args) or function_name - if len(preview) > 30: - preview = preview[:27] + "..." spinner = KawaiiSpinner(f"{face} {emoji} {preview}", spinner_type='dots', print_fn=self._print_fn) spinner.start() _spinner_result = None