diff --git a/scripts/release.py b/scripts/release.py index 7873b868e5..b0612f09ad 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -71,6 +71,7 @@ AUTHOR_MAP = { "16443023+stablegenius49@users.noreply.github.com": "stablegenius49", "fqsy1416@gmail.com": "EKKOLearnAI", "octo-patch@github.com": "octo-patch", + "math0r-be@github.com": "math0r-be", "simbamax99@gmail.com": "simbam99", "iris@growthpillars.co": "irispillars", "185121704+stablegenius49@users.noreply.github.com": "stablegenius49", diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index f7eacb6859..899fa7db85 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -1807,3 +1807,112 @@ def test_model_options_propagates_list_exception(monkeypatch): assert "error" in resp assert resp["error"]["code"] == 5033 assert "catalog blew up" in resp["error"]["message"] + + +# --------------------------------------------------------------------------- +# prompt.submit — auto-title +# --------------------------------------------------------------------------- + +class _ImmediateThread: + """Runs the target callable synchronously so assertions can follow.""" + + def __init__(self, target=None, daemon=None): + self._target = target + + def start(self): + self._target() + + +def test_prompt_submit_auto_titles_session_on_complete(monkeypatch): + """maybe_auto_title is called after a successful (complete) prompt.""" + + class _Agent: + def run_conversation(self, prompt, conversation_history=None, stream_callback=None): + return { + "final_response": "Rome was founded in 753 BC.", + "messages": [ + {"role": "user", "content": "Tell me about Rome"}, + {"role": "assistant", "content": "Rome was founded in 753 BC."}, + ], + } + + server._sessions["sid"] = _session(agent=_Agent()) + monkeypatch.setattr(server.threading, "Thread", _ImmediateThread) + monkeypatch.setattr(server, "_emit", lambda *args, **kwargs: None) + monkeypatch.setattr(server, "make_stream_renderer", lambda cols: None) + monkeypatch.setattr(server, "render_message", lambda raw, cols: None) + monkeypatch.setattr(server, "_get_db", lambda: None) + + with patch("agent.title_generator.maybe_auto_title") as mock_title: + server.handle_request( + { + "id": "1", + "method": "prompt.submit", + "params": {"session_id": "sid", "text": "Tell me about Rome"}, + } + ) + + mock_title.assert_called_once() + args = mock_title.call_args.args + assert args[1] == "session-key" + assert args[2] == "Tell me about Rome" + assert args[3] == "Rome was founded in 753 BC." + + +def test_prompt_submit_skips_auto_title_when_interrupted(monkeypatch): + """maybe_auto_title must NOT be called when the agent was interrupted.""" + + class _Agent: + def run_conversation(self, prompt, conversation_history=None, stream_callback=None): + return { + "final_response": "partial answer", + "interrupted": True, + "messages": [], + } + + server._sessions["sid"] = _session(agent=_Agent()) + monkeypatch.setattr(server.threading, "Thread", _ImmediateThread) + monkeypatch.setattr(server, "_emit", lambda *args, **kwargs: None) + monkeypatch.setattr(server, "make_stream_renderer", lambda cols: None) + monkeypatch.setattr(server, "render_message", lambda raw, cols: None) + monkeypatch.setattr(server, "_get_db", lambda: None) + + with patch("agent.title_generator.maybe_auto_title") as mock_title: + server.handle_request( + { + "id": "1", + "method": "prompt.submit", + "params": {"session_id": "sid", "text": "Tell me about Rome"}, + } + ) + + mock_title.assert_not_called() + + +def test_prompt_submit_skips_auto_title_when_response_empty(monkeypatch): + """maybe_auto_title must NOT be called when the agent returns an empty reply.""" + + class _Agent: + def run_conversation(self, prompt, conversation_history=None, stream_callback=None): + return { + "final_response": "", + "messages": [], + } + + server._sessions["sid"] = _session(agent=_Agent()) + monkeypatch.setattr(server.threading, "Thread", _ImmediateThread) + monkeypatch.setattr(server, "_emit", lambda *args, **kwargs: None) + monkeypatch.setattr(server, "make_stream_renderer", lambda cols: None) + monkeypatch.setattr(server, "render_message", lambda raw, cols: None) + monkeypatch.setattr(server, "_get_db", lambda: None) + + with patch("agent.title_generator.maybe_auto_title") as mock_title: + server.handle_request( + { + "id": "1", + "method": "prompt.submit", + "params": {"session_id": "sid", "text": "Tell me about Rome"}, + } + ) + + mock_title.assert_not_called() diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 30531aab28..ae5d58579e 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2321,6 +2321,26 @@ def _(rid, params: dict) -> dict: payload["rendered"] = rendered _emit("message.complete", sid, payload) + if ( + status == "complete" + and isinstance(raw, str) + and raw.strip() + and isinstance(text, str) + and text.strip() + ): + try: + from agent.title_generator import maybe_auto_title + + maybe_auto_title( + _get_db(), + session.get("session_key") or sid, + text, + raw, + session.get("history", []), + ) + except Exception: + pass + # CLI parity: when voice-mode TTS is on, speak the agent reply # (cli.py:_voice_speak_response). Only the final text — tool # calls / reasoning already stream separately and would be