diff --git a/agent/title_generator.py b/agent/title_generator.py index 3f617093c0b60..fc666b130fcd2 100644 --- a/agent/title_generator.py +++ b/agent/title_generator.py @@ -90,6 +90,7 @@ def auto_title_session( assistant_response: str, failure_callback: Optional[FailureCallback] = None, main_runtime: dict = None, + on_title=None, ) -> None: """Generate and set a session title if one doesn't already exist. @@ -119,6 +120,11 @@ def auto_title_session( try: session_db.set_session_title(session_id, title) logger.debug("Auto-generated session title: %s", title) + if on_title: + try: + on_title(title) + except Exception: + pass except Exception as e: logger.debug("Failed to set auto-generated title: %s", e) @@ -131,6 +137,7 @@ def maybe_auto_title( conversation_history: list, failure_callback: Optional[FailureCallback] = None, main_runtime: dict = None, + on_title=None, ) -> None: """Fire-and-forget title generation after the first exchange. @@ -152,7 +159,7 @@ def maybe_auto_title( thread = threading.Thread( target=auto_title_session, args=(session_db, session_id, user_message, assistant_response), - kwargs={"failure_callback": failure_callback, "main_runtime": main_runtime}, + kwargs={"failure_callback": failure_callback, "main_runtime": main_runtime, "on_title": on_title}, daemon=True, name="auto-title", ) diff --git a/cli.py b/cli.py index 1f2f26fc9f7a5..30ad81f48b8f0 100644 --- a/cli.py +++ b/cli.py @@ -3498,6 +3498,7 @@ def _init_agent(self, *, model_override: str = None, runtime_override: dict = No try: self._session_db.set_session_title(self.session_id, self._pending_title) _cprint(f" Session title applied: {self._pending_title}") + self._set_terminal_title(self._pending_title) self._pending_title = None except (ValueError, Exception) as e: _cprint(f" Could not apply pending title: {e}") @@ -4119,6 +4120,11 @@ def _write_osc52_clipboard(self, text: str) -> None: sys.stdout.write(seq) sys.stdout.flush() + def _set_terminal_title(self, title: str) -> None: + """Set the terminal tab/window title via OSC 0 escape sequence.""" + sys.stdout.write(f"\x1b]0;Hermes: {title}\x07") + sys.stdout.flush() + def _handle_copy_command(self, cmd_original: str) -> None: """Handle /copy [number] — copy assistant output to clipboard.""" parts = cmd_original.split(maxsplit=1) @@ -4899,6 +4905,8 @@ def _handle_resume_command(self, cmd_original: str) -> None: self.agent._invalidate_system_prompt() title_part = f" \"{session_meta['title']}\"" if session_meta.get("title") else "" + if session_meta.get("title"): + self._set_terminal_title(session_meta["title"]) msg_count = len([m for m in self.conversation_history if m.get("role") == "user"]) if self.conversation_history: _cprint( @@ -6137,6 +6145,7 @@ def process_command(self, command: str) -> bool: try: if self._session_db.set_session_title(self.session_id, new_title): _cprint(f" Session title set: {new_title}") + self._set_terminal_title(new_title) else: _cprint(" Session not found in database.") except ValueError as e: @@ -8844,6 +8853,7 @@ def run_agent(): "api_key": self.api_key, "api_mode": self.api_mode, }, + on_title=self._set_terminal_title, ) except Exception: pass @@ -9256,6 +9266,14 @@ def run(self): if self._resumed: if self._preload_resumed_session(): self._display_resumed_history() + # Set terminal title from loaded session if it has one + if self._session_db: + try: + t = self._session_db.get_session_title(self.session_id) + if t: + self._set_terminal_title(t) + except Exception: + pass try: from hermes_cli.skin_engine import get_active_skin