diff --git a/cli.py b/cli.py index e88f9312f3c..6091e3907bd 100644 --- a/cli.py +++ b/cli.py @@ -3698,7 +3698,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): self.console = Console() self.config = CLI_CONFIG self.compact = compact if compact is not None else CLI_CONFIG["display"].get("compact", False) - # tool_progress: "off", "generic", "new", "all", "verbose" (from config.yaml display section) + # tool_progress: "off", "new", "all", "verbose" (from config.yaml display section) # YAML 1.1 parses bare `off` as boolean False — normalise to string. _raw_tp = CLI_CONFIG["display"].get("tool_progress", "all") self.tool_progress_mode = "off" if _raw_tp is False else str(_raw_tp) @@ -9191,7 +9191,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): explicit ``-v``/``--verbose`` flag and the ``/verbose-logging`` toggle. See PR #6a1aa420e for the history that decoupled them. """ - cycle = ["off", "generic", "new", "all", "verbose"] + cycle = ["off", "new", "all", "verbose"] try: idx = cycle.index(self.tool_progress_mode) except ValueError: @@ -9212,7 +9212,6 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): from hermes_cli.colors import Colors as _Colors labels = { "off": f"{_Colors.DIM}Tool progress: OFF{_Colors.RESET} — silent mode, just the final response.", - "generic": f"{_Colors.YELLOW}Tool progress: GENERIC{_Colors.RESET} — show friendly placeholders instead of tool details.", "new": f"{_Colors.YELLOW}Tool progress: NEW{_Colors.RESET} — show each new tool (skip repeats).", "all": f"{_Colors.GREEN}Tool progress: ALL{_Colors.RESET} — show every tool call.", "verbose": f"{_Colors.BOLD}{_Colors.GREEN}Tool progress: VERBOSE{_Colors.RESET} — full args, results, and think blocks.", diff --git a/gateway/assets/status_phrases.yaml b/gateway/assets/status_phrases.yaml index 3aed7859968..3a58dadf65e 100644 --- a/gateway/assets/status_phrases.yaml +++ b/gateway/assets/status_phrases.yaml @@ -1,198 +1,52 @@ -# Default phrases for generic gateway visibility surfaces. -# -# These are Hermes UI/status surfaces, not app/vendor/domain buckets. Keep them -# generic: do not mention Jira, Confluence, Gmail, local paths, model names, -# secrets, or tool arguments. User config can append/replace these via: -# -# display: -# status_phrases: -# mode: append # append | replace -# thinking: -# - one sec, thinking this through -# platforms: -# whatsapp: -# status_phrases: -# status: -# - still on it - -thinking: - - one sec, thinking it through - - thinking this through - - hmm, working through it - - one sec, lining it up - - checking the shape of it - - following the thread - - sorting the pieces - - thinking through the tradeoffs - - one sec, making sure this fits - - working out the clean version - - holding the thought for a second - - checking the logic - - one sec, there’s a detail here - - turning it over quickly - - making sure I’m not missing a bit - - mapping the moving pieces - - letting the idea settle for a sec - - checking the edge cases - - narrowing the answer down - - one sec, this has a wrinkle - - tracing the dependency chain - - sorting signal from noise - - making the answer less wrong - - checking the assumption - - building the mental model - - sanity-checking it - - working through the awkward part - - finding the clean path - - one sec, connecting the dots - - checking whether that actually follows - -tool: - - checking that now - - looking into it - - one sec, checking the details - - working through the lookup - - checking the actual thing - - pulling the thread - - one sec, doing the lookup - - checking the source - - looking at the live data - - grabbing the relevant bit - - checking what’s there - - one sec, verifying it - - reading through it - - checking the current state - - looking for the signal - - checking the record - - digging through the source - - fetching the useful bit - - seeing what it says - - looking under the hood - - checking the real state - - pulling current context - - scanning the relevant output - - checking the available info - - one sec, comparing the pieces - - checking the latest version - - confirming against the source - - looking for the exact bit - - checking where this lands - - one sec, validating it - -command: - - running it now - - checking what the machine says - - one sec, running the command - - running the check - - asking the machine directly - - letting it run for a sec - - checking the output - - running the useful bit - - one sec, getting a real result - - testing it locally - - checking the logs - - running the verification - - waiting on the command - - checking the system state - - letting the process finish - - running the test - - asking the shell - - waiting for the output - - checking the result - - letting the command complete - - running the boring bit - - checking the terminal output - - one sec, the machine is thinking - - running the local check - - waiting on the process - - checking whether it passes - - letting the job finish - - watching the output - - verifying it directly - - checking the exit status - -interim: - - one sec, there’s a bit more here - - still shaping the answer - - stitching this together - - almost got the useful version - - one sec, connecting the pieces - - working this into a clean answer - - still narrowing it down - - pulling this into shape - - one sec, checking the last part - - getting the answer into a useful form - - almost there - - one more bit to check - - turning this into something readable - - still pulling it together - - one sec, making this less messy - - checking the last thread - - shaping the final answer - - joining the dots - - just tightening the answer - - one sec, cleaning this up - - still organizing it - - almost got the shape - - one more pass - - making the useful part clearer - - still stitching - - checking the last detail - - making sure this lands cleanly - - one sec, summarizing the useful bit - - pulling the answer together - - final bit of cleanup - status: - - still on it - - still working through it - - this is taking a bit, still running - - still checking - - not stuck, just still running - - waiting on the slow part - - still alive, still working - - letting it finish - - still processing this - - one sec, this is still going - - still making progress - - waiting for the result - - still here, checking - - the slow bit is still running - - still working on the useful answer - - no result yet, still waiting - - still running the check - - waiting on the backend bit - - this one needs a minute - - still moving - - still going, not frozen - - waiting for the useful output - - still chewing through it - - one sec, long bit is still running - - still waiting on the result - - working through the slow step - - this is taking longer than usual - - still checking, no panic - - still in progress - - letting the slow thing finish - +- still on it +- still working through it +- this is taking a bit, still running +- still checking +- not stuck, just still running +- waiting on the slow part +- still alive, still working +- letting it finish +- still processing this +- one sec, this is still going +- still making progress +- waiting for the result +- still here, checking +- the slow bit is still running +- still working on the useful answer +- no result yet, still waiting +- still running the check +- waiting on the backend bit +- this one needs a minute +- still moving +- still going, not frozen +- waiting for the useful output +- still chewing through it +- one sec, long bit is still running +- still waiting on the result +- working through the slow step +- this is taking longer than usual +- still checking, no panic +- still in progress +- letting the slow thing finish generic: - - on it - - one sec - - checking that now - - give me a sec - - looking into it - - working through it - - checking - - still working - - one moment - - taking a look - - got it, checking - - give me a moment - - looking now - - sorting it - - checking the useful bit - - one sec, working on it - - taking a proper look - - checking the details - - working it out - - still looking +- on it +- one sec +- checking that now +- give me a sec +- looking into it +- working through it +- checking +- still working +- one moment +- taking a look +- got it, checking +- give me a moment +- looking now +- sorting it +- checking the useful bit +- one sec, working on it +- taking a proper look +- checking the details +- working it out +- still looking diff --git a/gateway/display_config.py b/gateway/display_config.py index 088d4381d81..aebda8cd8ba 100644 --- a/gateway/display_config.py +++ b/gateway/display_config.py @@ -234,7 +234,7 @@ def _normalise(setting: str, value: Any) -> Any: if value is True: return "all" val = str(value).lower() - return val if val in {"off", "generic", "new", "all", "verbose"} else "all" + return val if val in {"off", "new", "all", "verbose"} else "all" if setting in { "show_reasoning", "streaming", @@ -245,11 +245,7 @@ def _normalise(setting: str, value: Any) -> Any: }: if isinstance(value, str): val = value.strip().lower() - if val == "generic" and setting in { - "thinking_progress", - "interim_assistant_messages", - "long_running_notifications", - }: + if val == "generic" and setting == "long_running_notifications": return "generic" return val in {"true", "1", "yes", "on", "raw", "verbose"} return bool(value) diff --git a/gateway/run.py b/gateway/run.py index 9b5de5cf478..f6c14f33ca4 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -16499,6 +16499,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew *, default: bool = False, require_platform_override_for: set[Any] | None = None, + allow_generic: bool = False, ) -> str: """Return off|raw|generic for a gateway visibility surface.""" if require_platform_override_for: @@ -16514,7 +16515,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew return "off" value = resolve_display_setting(user_config, platform_key, setting, default) if isinstance(value, str) and value.strip().lower() == "generic": - return "generic" + return "generic" if allow_generic else "off" return "raw" if bool(value) else "off" def _generic_status_phrase(kind: str, *, tool_name: str | None = None, preview: str | None = None, args: Any = None) -> str: @@ -16689,10 +16690,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew if not _thinking_enabled: return thinking_text = preview if tool_name == "_thinking" else tool_name - if _thinking_mode == "generic": - msg = _generic_status_phrase("thinking", preview=str(thinking_text or "")) - else: - msg = f"💬 {thinking_text}" if thinking_text else None + msg = f"💬 {thinking_text}" if thinking_text else None if msg: progress_queue.put(msg) return @@ -16727,17 +16725,6 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew return last_tool[0] = tool_name - if tool_progress_generic: - progress_queue.put( - _generic_status_phrase( - "tool", - tool_name=str(tool_name or ""), - preview=str(preview or ""), - args=args, - ) - ) - return - # Build progress message with primary argument preview from agent.display import get_tool_emoji emoji = get_tool_emoji(tool_name, default="⚙️") @@ -17537,11 +17524,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew def _interim_assistant_cb(text: str, *, already_streamed: bool = False) -> None: if not _run_still_current(): return - display_text = ( - _generic_status_phrase("interim", preview=str(text or "")) - if interim_assistant_messages_mode == "generic" - else text - ) + display_text = text if _stream_consumer is not None: if already_streamed: _stream_consumer.on_segment_break() @@ -18727,6 +18710,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew _long_running_mode = _display_surface_mode( "long_running_notifications", default=True, + allow_generic=True, ) if _long_running_mode == "off": _NOTIFY_INTERVAL = None diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index 5732d445e6a..0d7be83cf9e 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -2936,7 +2936,6 @@ class GatewaySlashCommandsMixin: cycle = ["off", "new", "all", "verbose", "log"] descriptions = { "off": t("gateway.verbose.mode_off"), - "generic": "GENERIC — show human-friendly placeholders instead of tool details.", "new": t("gateway.verbose.mode_new"), "all": t("gateway.verbose.mode_all"), "verbose": t("gateway.verbose.mode_verbose"), diff --git a/gateway/status_phrases.py b/gateway/status_phrases.py index 3c3b2f92991..b7c490273e1 100644 --- a/gateway/status_phrases.py +++ b/gateway/status_phrases.py @@ -1,8 +1,8 @@ """Human-friendly generic gateway status phrases. These helpers deliberately avoid relaying raw model scratch text. They turn -Hermes' own gateway visibility surfaces (thinking/tool/command/interim/status) -into short status lines suitable for chat surfaces. +Hermes' long-running gateway status surface into short status lines suitable +for chat surfaces. Built-in defaults live in ``gateway/assets/status_phrases.yaml``. Users can add portable, profile-relative phrase catalogs under ``HERMES_HOME`` either by using @@ -36,20 +36,15 @@ import yaml from hermes_constants import get_hermes_home -# These are Hermes UI surfaces, not app/vendor/domain buckets. Do not add -# Jira/Confluence/Gmail/etc. categories here; tool names are only used to choose -# between the built-in "tool" and "command" surfaces. -_STATUS_SURFACES = ("thinking", "tool", "command", "interim", "status", "generic") -_COMMAND_TOOL_NAMES = {"terminal", "execute_code", "process"} +# These are Hermes UI surfaces, not app/vendor/domain buckets. Keep this +# long-running-only: regular tool/thinking/interim chatter is intentionally not +# rewritten into generic placeholders because that gets noisy fast in chat. +_STATUS_SURFACES = ("status", "generic") _MAX_CUSTOM_PHRASES_PER_SURFACE = 80 _MAX_PHRASE_CHARS = 160 _CONVENTIONAL_RELATIVE_PATHS = ("status_phrases.yaml", "status_phrases") _FALLBACK_PHRASES: dict[str, list[str]] = { - "thinking": ["one sec, thinking it through", "thinking this through", "checking the logic"], - "tool": ["checking that now", "looking into it", "checking the source"], - "command": ["running it now", "checking what the machine says", "checking the output"], - "interim": ["still shaping the answer", "stitching this together", "almost there"], "status": ["still on it", "still working through it", "waiting for the result"], "generic": ["on it", "one sec", "checking that now"], } @@ -198,15 +193,6 @@ def classify_status_context( normalized = str(kind or "").strip().lower() if normalized in {"heartbeat", "waiting", "long_running", "status"}: return "status" - if normalized in {"thinking", "_thinking", "reasoning"}: - return "thinking" - if normalized in {"interim", "interim_assistant"}: - return "interim" - if normalized in {"command", "terminal"}: - return "command" - if normalized == "tool": - name = (tool_name or "").strip().lower() - return "command" if name in _COMMAND_TOOL_NAMES else "tool" return "generic" diff --git a/hermes_cli/config.py b/hermes_cli/config.py index b8e3bc1cebd..5e0defc75fa 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -1773,11 +1773,11 @@ DEFAULT_CONFIG = { # applies where tool_progress is already enabled. Per-platform override # via display.platforms..tool_progress_grouping. "tool_progress_grouping": "accumulate", - # Optional custom phrases for display.* generic visibility modes. + # Optional custom phrases for generic long-running status messages. # Built-in defaults live in gateway/assets/status_phrases.yaml. Users # can set `path`/`paths` to HERMES_HOME-relative YAML files/directories # (or rely on conventional status_phrases.yaml / status_phrases/*.yaml). - # Keys: thinking, tool, command, interim, status, generic. Use + # Keys: status, generic. Use # mode: "append" (default) to add phrases, or "replace" to fully # replace configured surfaces. Per-platform overrides live under # display.platforms..status_phrases. @@ -5346,7 +5346,7 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A if old_enabled and old_enabled.lower() in {"false", "0", "no"}: display["tool_progress"] = "off" results["config_added"].append("display.tool_progress=off (from HERMES_TOOL_PROGRESS=false)") - elif old_mode and old_mode.lower() in {"generic", "new", "all", "verbose"}: + elif old_mode and old_mode.lower() in {"new", "all", "verbose"}: display["tool_progress"] = old_mode.lower() results["config_added"].append(f"display.tool_progress={old_mode.lower()} (from HERMES_TOOL_PROGRESS_MODE)") else: diff --git a/tests/cli/test_cli_init.py b/tests/cli/test_cli_init.py index 35e17251de8..a990f6bf342 100644 --- a/tests/cli/test_cli_init.py +++ b/tests/cli/test_cli_init.py @@ -99,7 +99,7 @@ class TestVerboseAndToolProgress: def test_tool_progress_mode_is_string(self): cli = _make_cli() assert isinstance(cli.tool_progress_mode, str) - assert cli.tool_progress_mode in {"off", "generic", "new", "all", "verbose"} + assert cli.tool_progress_mode in {"off", "new", "all", "verbose"} class TestFallbackChainInit: diff --git a/tests/gateway/test_display_config.py b/tests/gateway/test_display_config.py index 7b54d571315..f95c8bdb496 100644 --- a/tests/gateway/test_display_config.py +++ b/tests/gateway/test_display_config.py @@ -149,13 +149,13 @@ class TestYAMLNormalisation: config = {"display": {"tool_progress": True}} assert resolve_display_setting(config, "telegram", "tool_progress") == "all" - def test_tool_progress_generic_is_first_class_mode(self): + def test_tool_progress_generic_is_not_a_mode(self): from gateway.display_config import resolve_display_setting config = {"display": {"platforms": {"whatsapp": {"tool_progress": "generic"}}}} - assert resolve_display_setting(config, "whatsapp", "tool_progress") == "generic" + assert resolve_display_setting(config, "whatsapp", "tool_progress") == "all" - def test_chatter_visibility_surfaces_accept_generic_mode(self): + def test_only_long_running_visibility_accepts_generic_mode(self): from gateway.display_config import resolve_display_setting config = { @@ -169,8 +169,8 @@ class TestYAMLNormalisation: } } } - assert resolve_display_setting(config, "whatsapp", "thinking_progress") == "generic" - assert resolve_display_setting(config, "whatsapp", "interim_assistant_messages") == "generic" + assert resolve_display_setting(config, "whatsapp", "thinking_progress") is False + assert resolve_display_setting(config, "whatsapp", "interim_assistant_messages") is False assert resolve_display_setting(config, "whatsapp", "long_running_notifications") == "generic" def test_thinking_progress_string_false_normalised_to_false(self): diff --git a/tests/gateway/test_status_phrases.py b/tests/gateway/test_status_phrases.py index 633474c915f..d59b6679b28 100644 --- a/tests/gateway/test_status_phrases.py +++ b/tests/gateway/test_status_phrases.py @@ -7,72 +7,59 @@ from gateway.status_phrases import ( ) -def test_terminal_tool_uses_command_surface_bucket(): - assert classify_status_context("tool", tool_name="terminal") == "command" - assert classify_status_context("command") == "command" +def test_long_running_context_uses_status_bucket(): + assert classify_status_context("status") == "status" + assert classify_status_context("heartbeat") == "status" + assert classify_status_context("long_running") == "status" -def test_regular_tool_uses_tool_surface_bucket_not_domain_bucket(): - assert classify_status_context("tool", tool_name="web_search", preview="weather") == "tool" - assert classify_status_context("tool", tool_name="mcp_atlassian_infohealth_getJiraIssue") == "tool" +def test_non_status_context_falls_back_to_generic_bucket(): + assert classify_status_context("tool", tool_name="terminal") == "generic" + assert classify_status_context("thinking") == "generic" + assert classify_status_context("interim_assistant") == "generic" -def test_interim_uses_interim_surface_bucket(): - assert classify_status_context("interim_assistant") == "interim" - - -def test_generic_phrase_does_not_leak_raw_thinking_text(): +def test_status_phrase_does_not_leak_raw_preview_or_args(): msg = choose_status_phrase( - "thinking", + "status", preview="actual private scratch text should not be sent", + args={"secret": "SECRET-123"}, rng=random.Random(4), ) assert "actual private scratch" not in msg + assert "SECRET-123" not in msg assert msg -def test_generic_tool_phrase_does_not_leak_vendor_or_args(): - msg = choose_status_phrase( - "tool", - tool_name="mcp_atlassian_infohealth_getJiraIssue", - args={"issueIdOrKey": "SECRET-123"}, - rng=random.Random(1), - ) - - assert "jira" not in msg.lower() - assert "confluence" not in msg.lower() - assert "atlassian" not in msg.lower() - assert "SECRET-123" not in msg - - -def test_generic_phrase_avoids_recent_repetition(): +def test_status_phrase_avoids_recent_repetition(): recent: list[str] = [] - first = choose_status_phrase("tool", tool_name="web_search", rng=random.Random(2), recent=recent) - second = choose_status_phrase("tool", tool_name="web_search", rng=random.Random(2), recent=recent) + first = choose_status_phrase("status", rng=random.Random(2), recent=recent) + second = choose_status_phrase("status", rng=random.Random(2), recent=recent) assert first != second assert recent[-2:] == [first, second] -def test_builtin_catalog_is_loaded_from_external_asset_and_is_not_tiny(): +def test_builtin_catalog_is_loaded_from_external_asset_and_is_status_only(): catalog = resolve_status_phrase_catalog({}, "whatsapp") - for surface in ("thinking", "tool", "command", "interim", "status"): - assert len(catalog[surface]) >= 25, surface + assert set(catalog) == {"status", "generic"} + assert len(catalog["status"]) >= 25 + assert len(catalog["generic"]) >= 10 def test_relative_status_phrase_path_loads_from_hermes_home(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path)) phrase_file = tmp_path / "phrases.yaml" - phrase_file.write_text("mode: replace\ntool:\n - relative safe tool text\n", encoding="utf-8") + phrase_file.write_text("mode: replace\nstatus:\n - relative safe status text\n", encoding="utf-8") catalog = resolve_status_phrase_catalog( {"display": {"status_phrases": {"path": "phrases.yaml"}}}, "whatsapp", ) - assert catalog["tool"] == ["relative safe tool text"] + assert catalog["status"] == ["relative safe status text"] def test_status_phrase_path_can_load_relative_directory(tmp_path, monkeypatch): @@ -92,7 +79,7 @@ def test_status_phrase_path_can_load_relative_directory(tmp_path, monkeypatch): def test_absolute_or_parent_phrase_paths_are_ignored(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path)) outside = tmp_path.parent / "outside-phrases.yaml" - outside.write_text("mode: replace\ntool:\n - should not load\n", encoding="utf-8") + outside.write_text("mode: replace\nstatus:\n - should not load\n", encoding="utf-8") catalog = resolve_status_phrase_catalog( {"display": {"status_phrases": {"path": str(outside)}}}, @@ -103,8 +90,8 @@ def test_absolute_or_parent_phrase_paths_are_ignored(tmp_path, monkeypatch): "whatsapp", ) - assert catalog["tool"] != ["should not load"] - assert escaped["tool"] != ["should not load"] + assert catalog["status"] != ["should not load"] + assert escaped["status"] != ["should not load"] def test_conventional_relative_status_phrase_file_is_loaded(tmp_path, monkeypatch): @@ -119,23 +106,23 @@ def test_conventional_relative_status_phrase_file_is_loaded(tmp_path, monkeypatc assert catalog["status"] == ["conventional status text"] -def test_global_custom_phrase_catalog_appends_to_builtin(): +def test_global_custom_status_phrase_catalog_appends_to_builtin(): catalog = resolve_status_phrase_catalog( { "display": { "status_phrases": { - "thinking": ["custom thinking placeholder"], + "status": ["custom long-running placeholder"], } } }, "whatsapp", ) - assert "custom thinking placeholder" in catalog["thinking"] - assert len(catalog["thinking"]) > 1 + assert "custom long-running placeholder" in catalog["status"] + assert len(catalog["status"]) > 1 -def test_platform_custom_phrase_catalog_can_replace_surface(): +def test_platform_custom_status_phrase_catalog_can_replace_surface(): catalog = resolve_status_phrase_catalog( { "display": { @@ -143,7 +130,7 @@ def test_platform_custom_phrase_catalog_can_replace_surface(): "whatsapp": { "status_phrases": { "mode": "replace", - "tool": ["custom tool placeholder"], + "status": ["custom status placeholder"], } } } @@ -152,22 +139,21 @@ def test_platform_custom_phrase_catalog_can_replace_surface(): "whatsapp", ) - assert catalog["tool"] == ["custom tool placeholder"] - assert len(catalog["thinking"]) > 1 + assert catalog["status"] == ["custom status placeholder"] + assert len(catalog["generic"]) > 1 def test_choose_status_phrase_uses_custom_catalog_without_leaking_args(): catalog = resolve_status_phrase_catalog( - {"display": {"status_phrases": {"mode": "replace", "tool": ["custom safe tool text"]}}}, + {"display": {"status_phrases": {"mode": "replace", "status": ["custom safe status text"]}}}, "whatsapp", ) msg = choose_status_phrase( - "tool", - tool_name="web_search", + "status", args={"query": "SECRET SEARCH"}, catalog=catalog, ) - assert msg == "custom safe tool text" + assert msg == "custom safe status text" assert "SECRET" not in msg diff --git a/tests/gateway/test_verbose_command.py b/tests/gateway/test_verbose_command.py index 9340d7e1a90..b420a16047d 100644 --- a/tests/gateway/test_verbose_command.py +++ b/tests/gateway/test_verbose_command.py @@ -132,7 +132,7 @@ class TestVerboseCommand: Telegram's tier-1 preset overrides ``tool_progress`` to ``"off"`` so the platform stays final-answer-first by default on mobile inboxes. The - first ``/verbose`` invocation therefore cycles ``off → generic``. + first ``/verbose`` invocation therefore cycles ``off → new``. """ hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -147,10 +147,10 @@ class TestVerboseCommand: runner = _make_runner() result = await runner._handle_verbose_command(_make_event()) - # Telegram platform default is "off" → cycles to "generic" - assert "GENERIC" in result + # Telegram platform default is "off" → cycles to "new" + assert "NEW" in result saved = yaml.safe_load(config_path.read_text(encoding="utf-8")) - assert saved["display"]["platforms"]["telegram"]["tool_progress"] == "generic" + assert saved["display"]["platforms"]["telegram"]["tool_progress"] == "new" @pytest.mark.asyncio async def test_per_platform_isolation(self, tmp_path, monkeypatch): @@ -158,7 +158,7 @@ class TestVerboseCommand: Without a global tool_progress, each platform uses its built-in default — Telegram = 'off' (tier-1 inbox override), Slack = 'off' - (quiet Slack default). Both cycle to 'generic' on first /verbose. + (quiet Slack default). Both cycle to 'new' on first /verbose. """ hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -183,10 +183,10 @@ class TestVerboseCommand: saved = yaml.safe_load(config_path.read_text(encoding="utf-8")) platforms = saved["display"]["platforms"] - # Telegram: off -> generic (platform default = off, tier-1 inbox override) - assert platforms["telegram"]["tool_progress"] == "generic" - # Slack: off -> generic (first /verbose cycle from quiet default) - assert platforms["slack"]["tool_progress"] == "generic" + # Telegram: off -> new (platform default = off, tier-1 inbox override) + assert platforms["telegram"]["tool_progress"] == "new" + # Slack: off -> new (first /verbose cycle from quiet default) + assert platforms["slack"]["tool_progress"] == "new" @pytest.mark.asyncio async def test_no_config_file_returns_disabled(self, tmp_path, monkeypatch): diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 25f3706b0fe..8b2ab195b02 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2385,7 +2385,7 @@ def _load_memory_notifications() -> str: def _load_tool_progress_mode() -> str: env = os.environ.get("HERMES_TUI_TOOL_PROGRESS", "").strip().lower() - if env in {"off", "generic", "new", "all", "verbose"}: + if env in {"off", "new", "all", "verbose"}: return env raw = (_load_cfg().get("display") or {}).get("tool_progress", "all") if raw is False: @@ -2393,7 +2393,7 @@ def _load_tool_progress_mode() -> str: if raw is True: return "all" mode = str(raw or "all").strip().lower() - return mode if mode in {"off", "generic", "new", "all", "verbose"} else "all" + return mode if mode in {"off", "new", "all", "verbose"} else "all" def _load_enabled_toolsets() -> list[str] | None: @@ -9930,7 +9930,7 @@ def _(rid, params: dict) -> dict: return _ok(rid, {"key": key, "value": raw}) if key == "verbose": - cycle = ["off", "generic", "new", "all", "verbose"] + cycle = ["off", "new", "all", "verbose"] cur = ( session.get("tool_progress_mode", _load_tool_progress_mode()) if session