From 102b9716ed154782a7d17be720e0a4a888889156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Vall=C3=A9s?= Date: Tue, 3 Mar 2026 17:16:08 +0100 Subject: [PATCH 1/6] feat: Implement Telegram draft/progress messages (streaming) --- nanobot/channels/telegram.py | 38 ++++++++++++++++++++++++++---------- pyproject.toml | 5 ++++- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/nanobot/channels/telegram.py b/nanobot/channels/telegram.py index c290535..5f739e5 100644 --- a/nanobot/channels/telegram.py +++ b/nanobot/channels/telegram.py @@ -269,23 +269,41 @@ class TelegramChannel(BaseChannel): # Send text content if msg.content and msg.content != "[empty message]": + is_progress = msg.metadata.get("_progress", False) + draft_id = msg.metadata.get("message_id") + for chunk in _split_message(msg.content): try: html = _markdown_to_telegram_html(chunk) - await self._app.bot.send_message( - chat_id=chat_id, - text=html, - parse_mode="HTML", - reply_parameters=reply_params - ) - except Exception as e: - logger.warning("HTML parse failed, falling back to plain text: {}", e) - try: + if is_progress and draft_id: + await self._app.bot.send_message_draft( + chat_id=chat_id, + draft_id=draft_id, + text=html, + parse_mode="HTML" + ) + else: await self._app.bot.send_message( chat_id=chat_id, - text=chunk, + text=html, + parse_mode="HTML", reply_parameters=reply_params ) + except Exception as e: + logger.warning("HTML parse failed (or draft send failed), falling back to plain text: {}", e) + try: + if is_progress and draft_id: + await self._app.bot.send_message_draft( + chat_id=chat_id, + draft_id=draft_id, + text=chunk + ) + else: + await self._app.bot.send_message( + chat_id=chat_id, + text=chunk, + reply_parameters=reply_params + ) except Exception as e2: logger.error("Error sending Telegram message: {}", e2) diff --git a/pyproject.toml b/pyproject.toml index a22053c..42f6194 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "rich>=14.0.0,<15.0.0", "croniter>=6.0.0,<7.0.0", "dingtalk-stream>=0.24.0,<1.0.0", - "python-telegram-bot[socks]>=22.0,<23.0", + "python-telegram-bot[socks] @ git+https://github.com/python-telegram-bot/python-telegram-bot.git@master", "lark-oapi>=1.5.0,<2.0.0", "socksio>=1.0.0,<2.0.0", "python-socketio>=5.16.0,<6.0.0", @@ -63,6 +63,9 @@ nanobot = "nanobot.cli.commands:app" requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.metadata] +allow-direct-references = true + [tool.hatch.build.targets.wheel] packages = ["nanobot"] From 61f658e04519ea7e711e6be707765bfd8ee9257d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Vall=C3=A9s?= Date: Wed, 4 Mar 2026 12:11:18 +0100 Subject: [PATCH 2/6] add reasoning content to on progress message --- nanobot/agent/loop.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 65a62e5..5eea6e6 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -202,9 +202,16 @@ class AgentLoop: if response.has_tool_calls: if on_progress: - clean = self._strip_think(response.content) - if clean: - await on_progress(clean) + thoughts = [ + self._strip_think(response.content), + response.reasoning_content, + *(f"Thinking [{b.get('signature', '...')}]:\n{b.get('thought', '...')}" + for b in (response.thinking_blocks or []) if isinstance(b, dict) and "signature" in b) + ] + + if combined := "\n\n".join(filter(None, thoughts)): + await on_progress(combined) + await on_progress(self._tool_hint(response.tool_calls), tool_hint=True) tool_call_dicts = [ From ca1f41562c11aadb1e9db9bdaace83cd684db31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Vall=C3=A9s?= Date: Wed, 4 Mar 2026 13:19:35 +0100 Subject: [PATCH 3/6] Fix telegram stop typing if not final message --- nanobot/channels/telegram.py | 4 +++- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nanobot/channels/telegram.py b/nanobot/channels/telegram.py index 5f739e5..de95a15 100644 --- a/nanobot/channels/telegram.py +++ b/nanobot/channels/telegram.py @@ -225,7 +225,9 @@ class TelegramChannel(BaseChannel): logger.warning("Telegram bot not running") return - self._stop_typing(msg.chat_id) + # Only stop typing indicator for final responses + if not msg.metadata.get("_progress", False): + self._stop_typing(msg.chat_id) try: chat_id = int(msg.chat_id) diff --git a/pyproject.toml b/pyproject.toml index 42f6194..7ffe8f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "rich>=14.0.0,<15.0.0", "croniter>=6.0.0,<7.0.0", "dingtalk-stream>=0.24.0,<1.0.0", - "python-telegram-bot[socks] @ git+https://github.com/python-telegram-bot/python-telegram-bot.git@master", + "python-telegram-bot[socks]>=22.0,<23.0", "lark-oapi>=1.5.0,<2.0.0", "socksio>=1.0.0,<2.0.0", "python-socketio>=5.16.0,<6.0.0", From c27d2b15220b2cff00604c4143851b989792fedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Vall=C3=A9s?= Date: Thu, 5 Mar 2026 00:33:27 +0100 Subject: [PATCH 4/6] fix(agent): prevent tool hints from overwriting reasoning in streaming drafts --- nanobot/agent/loop.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 5eea6e6..fc1fd75 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -209,10 +209,13 @@ class AgentLoop: for b in (response.thinking_blocks or []) if isinstance(b, dict) and "signature" in b) ] - if combined := "\n\n".join(filter(None, thoughts)): - await on_progress(combined) - - await on_progress(self._tool_hint(response.tool_calls), tool_hint=True) + combined_thoughts = "\n\n".join(filter(None, thoughts)) + tool_hint_str = self._tool_hint(response.tool_calls) + + if combined_thoughts: + await on_progress(f"{combined_thoughts}\n\n{tool_hint_str}", tool_hint=True) + else: + await on_progress(tool_hint_str, tool_hint=True) tool_call_dicts = [ { From 33f59d8a37a963f5fa694435155f42621d9852ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Vall=C3=A9s?= Date: Thu, 5 Mar 2026 00:45:15 +0100 Subject: [PATCH 5/6] fix(agent): separate reasoning and tool hints to respect channel config --- nanobot/agent/loop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index fc1fd75..2f6a2bc 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -213,6 +213,7 @@ class AgentLoop: tool_hint_str = self._tool_hint(response.tool_calls) if combined_thoughts: + await on_progress(combined_thoughts) await on_progress(f"{combined_thoughts}\n\n{tool_hint_str}", tool_hint=True) else: await on_progress(tool_hint_str, tool_hint=True) From d32c6f946c5fd030ddfbbb645adb43b84a43d6ed Mon Sep 17 00:00:00 2001 From: Re-bin Date: Thu, 5 Mar 2026 15:17:30 +0000 Subject: [PATCH 6/6] fix(telegram): pin ptb>=22.6, fix double progress, clean up stale hatch config --- nanobot/agent/loop.py | 14 ++++++-------- nanobot/channels/telegram.py | 2 +- pyproject.toml | 5 +---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 2f6a2bc..7f129a2 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -205,18 +205,16 @@ class AgentLoop: thoughts = [ self._strip_think(response.content), response.reasoning_content, - *(f"Thinking [{b.get('signature', '...')}]:\n{b.get('thought', '...')}" - for b in (response.thinking_blocks or []) if isinstance(b, dict) and "signature" in b) + *( + f"Thinking [{b.get('signature', '...')}]:\n{b.get('thought', '...')}" + for b in (response.thinking_blocks or []) + if isinstance(b, dict) and "signature" in b + ), ] - combined_thoughts = "\n\n".join(filter(None, thoughts)) - tool_hint_str = self._tool_hint(response.tool_calls) - if combined_thoughts: await on_progress(combined_thoughts) - await on_progress(f"{combined_thoughts}\n\n{tool_hint_str}", tool_hint=True) - else: - await on_progress(tool_hint_str, tool_hint=True) + await on_progress(self._tool_hint(response.tool_calls), tool_hint=True) tool_call_dicts = [ { diff --git a/nanobot/channels/telegram.py b/nanobot/channels/telegram.py index de95a15..884b2d0 100644 --- a/nanobot/channels/telegram.py +++ b/nanobot/channels/telegram.py @@ -292,7 +292,7 @@ class TelegramChannel(BaseChannel): reply_parameters=reply_params ) except Exception as e: - logger.warning("HTML parse failed (or draft send failed), falling back to plain text: {}", e) + logger.warning("HTML parse failed, falling back to plain text: {}", e) try: if is_progress and draft_id: await self._app.bot.send_message_draft( diff --git a/pyproject.toml b/pyproject.toml index 674a1ef..41d0fbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "rich>=14.0.0,<15.0.0", "croniter>=6.0.0,<7.0.0", "dingtalk-stream>=0.24.0,<1.0.0", - "python-telegram-bot[socks]>=22.0,<23.0", + "python-telegram-bot[socks]>=22.6,<23.0", "lark-oapi>=1.5.0,<2.0.0", "socksio>=1.0.0,<2.0.0", "python-socketio>=5.16.0,<6.0.0", @@ -68,9 +68,6 @@ nanobot = "nanobot.cli.commands:app" requires = ["hatchling"] build-backend = "hatchling.build" -[tool.hatch.metadata] -allow-direct-references = true - [tool.hatch.build.targets.wheel] packages = ["nanobot"]