diff --git a/cli.py b/cli.py index 3cc4116b49ea1..14cf9b29f3ba4 100644 --- a/cli.py +++ b/cli.py @@ -4075,6 +4075,23 @@ def _normalize_moa_model(model: Optional[str]) -> tuple[Optional[str], Optional[ return None, model +class _VoiceInputMessage: + """Sentinel wrapper for voice-transcribed messages in ``_pending_input``. + + Distinguishes STT output from manually typed text while voice mode is + active, so the concise-voice-response prefix is applied only to messages + that actually came from the microphone (#65827). + """ + + __slots__ = ("text",) + + def __init__(self, text: str): + self.text = text + + def __str__(self) -> str: + return self.text + + class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): """ Interactive CLI for the Hermes Agent. @@ -11928,7 +11945,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): self._attached_images.clear() if hasattr(self, '_app') and self._app: self._app.invalidate() - self._pending_input.put(transcript) + self._pending_input.put(_VoiceInputMessage(transcript)) submitted = True elif result.get("success"): _cprint(f"{_DIM}No speech detected.{_RST}") @@ -12116,7 +12133,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): _cprint(f"\n{_DIM}Stop phrase detected — ending voice chat.{_RST}") self._disable_voice_mode() return - self._pending_input.put(transcript) + self._pending_input.put(_VoiceInputMessage(transcript)) submitted = True elif not result.get("success"): _cprint(f"\n{_DIM}Transcription failed: {result.get('error', 'Unknown error')}{_RST}") @@ -12820,7 +12837,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): except Exception: pass - def chat(self, message, images: list = None) -> Optional[str]: + def chat(self, message, images: list = None, voice_input: bool = False) -> Optional[str]: """ Send a message to the agent and get a response. @@ -12835,6 +12852,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): Args: message: The user's message (str or multimodal content list) images: Optional list of Path objects for attached images + voice_input: True when the message came from voice transcription + (gates the concise voice-response prefix, #65827) Returns: The agent's response, or None on error @@ -13062,7 +13081,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): # model responds concisely. The prefix is API-call-local only — # run_conversation persists the original clean user message. _voice_prefix = "" - if self._voice_mode and isinstance(message, str): + if voice_input and isinstance(message, str): _voice_prefix = ( "[Voice input — respond concisely and conversationally, " "2-3 sentences max. No code blocks or markdown.] " @@ -16226,7 +16245,13 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): except Exception: pass continue - + + # Voice-transcribed messages arrive wrapped in a sentinel + # so only genuine STT output gets the voice prefix (#65827). + is_voice_input = isinstance(user_input, _VoiceInputMessage) + if is_voice_input: + user_input = user_input.text + if not user_input: continue @@ -16322,7 +16347,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): app.invalidate() # Refresh status line try: - self.chat(user_input, images=submit_images or None) + self.chat(user_input, images=submit_images or None, voice_input=is_voice_input) finally: self._agent_running = False self._spinner_text = ""