Revert "Add Telegram speech mode"

This reverts commit c51c235faacbbed44994635d4743b97f7a143a17.

Anmol Malik committed May 28, 2026 at 23:43 UTC 013a374daa312d57e5d5b0341d008a1b40752f2b
7 files changed +4 -248
helpers/integration_commands.py
-8
@@ -70,12 +70,6 @@ COMMAND_REGISTRY: tuple[IntegrationCommandDef, ...] = (
70 "Configuration",
71 args_hint="[on|off]",
72 ),
73 - IntegrationCommandDef(
74 - "speech",
75 - "Enable or disable Telegram spoken replies.",
76 - "Configuration",
77 - args_hint="[on|off]",
78 - ),
73 IntegrationCommandDef(
74 "project",
75 "Show or switch the active project.",
@@ -200,8 +194,6 @@ def try_handle_command(context: "AgentContext", text: str) -> str | None:
194 return _handle_toggle(context, args, "telegram_stream_enabled", "Response streaming")
195 if command == "/tools":
196 return _handle_toggle(context, args, "telegram_tools_enabled", "Tool progress")
203 - if command == "/speech":
204 - return _handle_toggle(context, args, "telegram_speech_enabled", "Speech replies")
197 if command == "/project":
198 return _handle_project(context, args)
199 if command == "/model":
plugins/_telegram_integration/README.md
-3
@@ -24,7 +24,6 @@ This plugin connects one or more Telegram bots to Agent Zero. Each bot runs inde
24 - `/agent <profile>` switches the active agent profile when the current run is idle.
25 - `/model`, `/project`, and `/agent` show Telegram inline keyboard pickers when used without arguments.
26 - `/stream` toggles live response streaming; `/tools` toggles tool progress messages.
27 - - `/speech` toggles spoken replies. When enabled, final text replies also include Kokoro-generated Telegram audio when Kokoro TTS is available.
27 - `/send` or `/queue send` flushes queued messages for the current chat.
28 - `/steer <message>` sends an intervention to the active run.
29 - **Group support**
@@ -36,7 +35,6 @@ This plugin connects one or more Telegram bots to Agent Zero. Each bot runs inde
35 - **Reply delivery**
36 - Streams tool progress and response text through real Telegram messages updated with `editMessageText`.
37 - Tool progress and the AI response are separate messages; only the AI response replies to the user message.
39 - - Sends native Telegram media: photos, voice/audio files, videos, and document fallback.
38 - `tool_execute_after` intercepts the `response` tool — sends `break_loop=false` updates as separate intermediate Telegram messages.
39 - `process_chain_end` auto-sends the final response, with retry logic on failure.
40 - **Formatting**
@@ -65,7 +63,6 @@ This plugin connects one or more Telegram bots to Agent Zero. Each bot runs inde
63 - `helpers/telegram_client.py` — Low-level Telegram API wrapper: send text/file/photo, Markdown→HTML converter, keyboard builder, message splitting.
64 - `helpers/command_ui.py` — Telegram inline keyboard command pickers and callback handling.
65 - `helpers/draft_stream.py` — Editable Telegram message streaming for tool progress and response previews.
68 - - `helpers/speech.py` — Telegram speech-mode TTS synthesis and audio attachment generation.
66 - **Extensions**
67 - `extensions/python/job_loop/_10_telegram_bot.py` — Bot lifecycle manager, starts/stops bots on each tick.
68 - `extensions/python/message_loop_start/_45_telegram_draft_start.py` — Initializes Telegram response streaming.
plugins/_telegram_integration/extensions/python/process_chain_end/_55_telegram_reply.py
-10
@@ -32,17 +32,7 @@ class TelegramAutoReply(Extension):
32
33 try:
34 if response_text:
35 - from plugins._telegram_integration.helpers import speech
36 -
37 - speech_attachment, speech_warning = await speech.synthesize_attachment(
38 - context, response_text
39 - )
40 - if speech_attachment:
41 - attachments = [*attachments, speech_attachment]
35 await self._send_reply(context, response_text, attachments, keyboard)
43 - warning = speech.consume_warning(context, speech_warning)
44 - if warning:
45 - await self._send_reply(context, warning, [], None)
36 except Exception as e:
37 PrintStyle.error(f"Telegram auto-reply error: {format_error(e)}")
38 finally:
plugins/_telegram_integration/helpers/command_ui.py
+4 -33
@@ -13,7 +13,6 @@ from plugins._telegram_integration.helpers import telegram_client as tc
13 from plugins._telegram_integration.helpers.constants import (
14 CTX_TG_STREAM_ENABLED,
15 CTX_TG_TOOLS_ENABLED,
16 - CTX_TG_SPEECH_ENABLED,
16 CTX_TG_BOT,
17 CTX_TG_CHAT_ID,
18 CTX_TG_CHAT_TYPE,
@@ -86,17 +85,6 @@ async def handle_command(
85 args,
86 )
87 return True
89 - if command == "/speech":
90 - await send_toggle_picker(
91 - context,
92 - token,
93 - chat_id,
94 - reply_to_message_id,
95 - CTX_TG_SPEECH_ENABLED,
96 - "Speech replies",
97 - args,
98 - )
99 - return True
88 return False
89
90
@@ -141,8 +129,9 @@ async def handle_callback(
129 selected_context = await _select_session(context, _safe_int(value))
130 await edit_session_picker(selected_context or context, token, chat_id, message_id, 0, selected=bool(selected_context))
131 return True
144 - if kind in {"stream", "tools", "speech"} and action in {"on", "off"}:
145 - key, label = _toggle_key_label(kind)
132 + if kind in {"stream", "tools"} and action in {"on", "off"}:
133 + key = CTX_TG_STREAM_ENABLED if kind == "stream" else CTX_TG_TOOLS_ENABLED
134 + label = "Response streaming" if kind == "stream" else "Tool progress"
135 context.set_data(key, action == "on")
136 save_tmp_chat(context)
137 mark_dirty_for_context(context.id, reason=f"telegram.{kind}_toggle")
@@ -345,7 +334,7 @@ def _agent_view(
334
335 def _toggle_view(context: AgentContext, key: str, label: str) -> tuple[str, dict]:
336 enabled = _toggle_enabled(context, key)
348 - kind = _toggle_kind(key)
337 + kind = "stream" if key == CTX_TG_STREAM_ENABLED else "tools"
338 state = "enabled" if enabled else "disabled"
339 text = f"{_html(label)}: <b>{state}</b>"
340 rows = [[
@@ -553,27 +542,9 @@ def _paged_buttons(
542
543 def _toggle_enabled(context: AgentContext, key: str) -> bool:
544 value = context.get_data(key)
556 - if key == CTX_TG_SPEECH_ENABLED:
557 - return bool(value)
545 return True if value is None else bool(value)
546
547
561 -def _toggle_kind(key: str) -> str:
562 - if key == CTX_TG_STREAM_ENABLED:
563 - return "stream"
564 - if key == CTX_TG_TOOLS_ENABLED:
565 - return "tools"
566 - return "speech"
567 -
568 -
569 -def _toggle_key_label(kind: str) -> tuple[str, str]:
570 - if kind == "stream":
571 - return CTX_TG_STREAM_ENABLED, "Response streaming"
572 - if kind == "tools":
573 - return CTX_TG_TOOLS_ENABLED, "Tool progress"
574 - return CTX_TG_SPEECH_ENABLED, "Speech replies"
575 -
576 -
548 def _parse_toggle(args: str) -> bool | None:
549 value = (args or "").strip().lower()
550 if value in {"on", "enable", "enabled", "yes", "true", "1"}:
plugins/_telegram_integration/helpers/constants.py
-2
@@ -13,7 +13,6 @@ CTX_TG_TYPING_STOP = "_telegram_typing_stop"
13 CTX_TG_REPLY_TO = "_telegram_reply_to_message_id"
14 CTX_TG_STREAM_ENABLED = "telegram_stream_enabled"
15 CTX_TG_TOOLS_ENABLED = "telegram_tools_enabled"
16 -CTX_TG_SPEECH_ENABLED = "telegram_speech_enabled"
16
17 # Transient
18 CTX_TG_ATTACHMENTS = "_telegram_response_attachments"
@@ -26,4 +25,3 @@ CTX_TG_RESPONSE_LAST_UPDATE = "_telegram_response_last_update"
25 CTX_TG_ERROR_SENT = "_telegram_error_sent"
26 CTX_TG_HEARTBEAT_TASK = "_telegram_heartbeat_task"
27 CTX_TG_HEARTBEAT_STOP = "_telegram_heartbeat_stop"
29 -CTX_TG_SPEECH_WARNING_SENT = "_telegram_speech_warning_sent"
plugins/_telegram_integration/helpers/speech.py deleted
-80
@@ -1,80 +0,0 @@
1 -from __future__ import annotations
2 -
3 -import re
4 -import uuid
5 -
6 -from agent import AgentContext
7 -from helpers import files
8 -from plugins._telegram_integration.helpers.constants import (
9 - CTX_TG_SPEECH_ENABLED,
10 - CTX_TG_SPEECH_WARNING_SENT,
11 - DOWNLOAD_FOLDER,
12 -)
13 -
14 -
15 -MAX_SPEECH_CHARS = 1800
16 -
17 -
18 -def is_enabled(context: AgentContext) -> bool:
19 - return bool(context.get_data(CTX_TG_SPEECH_ENABLED))
20 -
21 -
22 -async def synthesize_attachment(context: AgentContext, text: str) -> tuple[str | None, str | None]:
23 - if not is_enabled(context):
24 - return None, None
25 -
26 - speech_text = _speech_text(text)
27 - if not speech_text:
28 - return None, "Speech mode is enabled, but there was no readable text to speak."
29 -
30 - try:
31 - from plugins._kokoro_tts.helpers import runtime
32 - except Exception:
33 - return None, _setup_message()
34 -
35 - if not runtime.is_globally_enabled():
36 - return None, _setup_message()
37 -
38 - try:
39 - audio = await runtime.synthesize_sentences([speech_text])
40 - except Exception as exc:
41 - return None, f"Speech mode is enabled, but text-to-speech failed: {exc}"
42 -
43 - if not audio:
44 - return None, "Speech mode is enabled, but text-to-speech returned no audio."
45 -
46 - name = f"telegram_speech_{context.id}_{uuid.uuid4().hex[:10]}.wav"
47 - relative_path = f"{DOWNLOAD_FOLDER}/{name}"
48 - files.write_file_base64(relative_path, audio)
49 - return files.get_abs_path_dockerized(relative_path), None
50 -
51 -
52 -def consume_warning(context: AgentContext, warning: str | None) -> str | None:
53 - if not warning:
54 - context.data.pop(CTX_TG_SPEECH_WARNING_SENT, None)
55 - return None
56 - if context.data.get(CTX_TG_SPEECH_WARNING_SENT):
57 - return None
58 - context.data[CTX_TG_SPEECH_WARNING_SENT] = True
59 - return warning
60 -
61 -
62 -def _setup_message() -> str:
63 - return (
64 - "Speech mode is enabled, but Kokoro TTS is not available. "
65 - "Enable the Kokoro TTS plugin from Agent Zero settings, then try again."
66 - )
67 -
68 -
69 -def _speech_text(text: str) -> str:
70 - value = str(text or "")
71 - value = re.sub(r"```.*?```", " ", value, flags=re.DOTALL)
72 - value = re.sub(r"`([^`]+)`", r"\1", value)
73 - value = re.sub(r"!\[([^\]]*)\]\([^)]+\)", r"\1", value)
74 - value = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", value)
75 - value = re.sub(r"<[^>]+>", " ", value)
76 - value = re.sub(r"[*_#>\-]+", " ", value)
77 - value = re.sub(r"\s+", " ", value).strip()
78 - if len(value) > MAX_SPEECH_CHARS:
79 - value = value[:MAX_SPEECH_CHARS].rstrip()
80 - return value
tests/test_telegram_speech_mode.py deleted
-112
@@ -1,112 +0,0 @@
1 -import asyncio
2 -
3 -from plugins._telegram_integration.extensions.python.process_chain_end import (
4 - _55_telegram_reply,
5 -)
6 -from plugins._telegram_integration.helpers import command_ui, speech
7 -from plugins._telegram_integration.helpers.constants import (
8 - CTX_TG_BOT,
9 - CTX_TG_REPLY_TO,
10 - CTX_TG_SPEECH_ENABLED,
11 -)
12 -
13 -
14 -class FakeContext:
15 - id = "ctx-speech"
16 -
17 - def __init__(self):
18 - self.data = {}
19 -
20 - def get_data(self, key):
21 - return self.data.get(key)
22 -
23 - def set_data(self, key, value):
24 - self.data[key] = value
25 -
26 -
27 -class FakeAgent:
28 - number = 0
29 -
30 - def __init__(self):
31 - self.context = FakeContext()
32 - self.context.data[CTX_TG_BOT] = "main"
33 - self.context.data[CTX_TG_REPLY_TO] = 456
34 -
35 -
36 -def test_speech_toggle_defaults_off():
37 - context = FakeContext()
38 -
39 - text, markup = command_ui._toggle_view(context, CTX_TG_SPEECH_ENABLED, "Speech replies")
40 -
41 - assert "Speech replies: <b>disabled</b>" == text
42 - assert markup["inline_keyboard"][0][0]["callback_data"] == "tg:speech:on"
43 - assert markup["inline_keyboard"][0][1]["callback_data"] == "tg:speech:off"
44 -
45 -
46 -def test_speech_text_is_cleaned_for_tts():
47 - cleaned = speech._speech_text(
48 - "**Hello** [`Agent Zero`](https://example.com)\n\n```python\nprint('skip')\n```"
49 - )
50 -
51 - assert cleaned == "Hello Agent Zero"
52 -
53 -
54 -def test_speech_synthesis_writes_audio_attachment(monkeypatch):
55 - context = FakeContext()
56 - context.data[CTX_TG_SPEECH_ENABLED] = True
57 - writes = []
58 -
59 - class FakeRuntime:
60 - @staticmethod
61 - def is_globally_enabled():
62 - return True
63 -
64 - @staticmethod
65 - async def synthesize_sentences(sentences):
66 - assert sentences == ["Hello from Agent Zero."]
67 - return "UklGRg=="
68 -
69 - monkeypatch.setattr(speech.files, "write_file_base64", lambda path, audio: writes.append((path, audio)))
70 - monkeypatch.setattr(speech.files, "get_abs_path_dockerized", lambda path: f"/a0/{path}")
71 - monkeypatch.setitem(__import__("sys").modules, "plugins._kokoro_tts.helpers.runtime", FakeRuntime)
72 -
73 - path, warning = asyncio.run(speech.synthesize_attachment(context, "Hello from Agent Zero."))
74 -
75 - assert warning is None
76 - assert path.startswith("/a0/usr/uploads/telegram_speech_ctx-speech_")
77 - assert path.endswith(".wav")
78 - assert writes[0][1] == "UklGRg=="
79 -
80 -
81 -def test_final_reply_includes_speech_attachment(monkeypatch):
82 - agent = FakeAgent()
83 - sent = []
84 -
85 - async def fake_synthesize(context, text):
86 - return "/a0/usr/uploads/reply.wav", None
87 -
88 - async def fake_send_reply(context, response_text, attachments, keyboard):
89 - sent.append(
90 - {
91 - "text": response_text,
92 - "attachments": attachments,
93 - "keyboard": keyboard,
94 - }
95 - )
96 -
97 - monkeypatch.setattr(_55_telegram_reply, "_extract_last_response", lambda context: "Final reply.")
98 - monkeypatch.setattr(speech, "synthesize_attachment", fake_synthesize)
99 -
100 - extension = _55_telegram_reply.TelegramAutoReply(agent=agent)
101 - extension._send_reply = fake_send_reply
102 -
103 - asyncio.run(extension.execute())
104 -
105 - assert sent == [
106 - {
107 - "text": "Final reply.",
108 - "attachments": ["/a0/usr/uploads/reply.wav"],
109 - "keyboard": None,
110 - }
111 - ]
112 - assert CTX_TG_REPLY_TO not in agent.context.data