Commit graph

8950 commits

Author SHA1 Message Date
Teknium
c66efcff32 chore(release): map @rak135 for PR #25960 salvage 2026-05-18 22:38:08 -07:00
Martin
417a653d9e fix(gateway): prevent Windows Telegram /restart leaving gateway stopped 2026-05-18 22:38:08 -07:00
Teknium
1d378605dd test+release: stub auth in test_telegram_documents fixture; map @kiranvk-2011 2026-05-18 22:37:28 -07:00
kiranvk2011
77c4675a50 fix(telegram): route image documents (.png/.jpg/.webp/.gif) through vision pipeline
When users send images as documents (Telegram file picker), they were
rejected with "Unsupported document type" because SUPPORTED_DOCUMENT_TYPES
only includes text/office formats. Add SUPPORTED_IMAGE_DOCUMENT_TYPES
to base.py and handle them in telegram.py before the document check.

- Add SUPPORTED_IMAGE_DOCUMENT_TYPES constant to base.py
- Add MIME reverse-lookup for image types in telegram.py
- Route image documents through cache_image_from_bytes + vision pipeline
- Handle media groups for image documents

Closes: #20128, #18620
2026-05-18 22:37:28 -07:00
konsisumer
a4fb0a3ac3 fix(cron): route Telegram cron deliveries to a dedicated topic via TELEGRAM_CRON_THREAD_ID
When Telegram topic mode is enabled, cron messages delivered to the bot's
root DM (TELEGRAM_HOME_CHANNEL without a thread id) land in the system
lobby — replies there are rebuffed with the lobby reminder and
reply_to_message_id is dropped, so users cannot interact with the cron
output (#24409).

Add an optional TELEGRAM_CRON_THREAD_ID env var that overrides
TELEGRAM_HOME_CHANNEL_THREAD_ID for cron deliveries only. Operators can
create a "Cron" forum topic in the DM, point this var at its thread id,
and replies to cron messages will land in that topic's existing session
instead of the lobby. The home-channel thread id (used elsewhere, e.g.
restart notifications) is unchanged, and explicit
deliver="telegram:chat:thread" targets continue to win over the env var.

Per the reporter's clarification on 2026-05-13, option (a) (cron-side
route to a dedicated topic + config knob) was chosen.

Fixes #24409
2026-05-18 22:36:11 -07:00
Teknium
032d4cafc4 chore(release): map @booker1207 for PR #25132 salvage 2026-05-18 22:35:28 -07:00
Booker
46ce3453c1 fix(telegram): gate profile bots by allowed topics 2026-05-18 22:35:28 -07:00
Teknium
efc37409aa test+release: fix test fixture for forum_commands; map @chromalinx 2026-05-18 22:34:48 -07:00
Dani
7682198178 fix(gateway): register Telegram commands for groups
Register Telegram bot commands across default, private, and group scopes so
the slash-command menu is available outside DMs.

Changes from review feedback:
- Add asyncio.Lock to prevent race condition in _ensure_forum_commands
- Extract MAX_COMMANDS_PER_SCOPE constant (30) to avoid magic number
- Upgrade error logging from debug->warning in forum registration
- Add tests covering lazy forum registration and concurrent safety
- Remove /start handler from this PR (separate feature)

Fixes review: needs_work (race, magic number, log levels, missing tests)
2026-05-18 22:34:48 -07:00
Teknium
38356cc98b chore(release): map @kunci115 for PR #27098 salvage 2026-05-18 22:32:00 -07:00
kunci115
4abaec18b8 test(send_message): add thread-not-found retry tests for Telegram topics
Three tests covering the #27012 fix:
- test_is_thread_not_found_matches_expected_errors
- test_text_send_retries_without_thread_id_on_thread_not_found
- test_disable_web_page_preview_not_leaked_to_media_sends

116/116 existing tests still pass (no regressions).
2026-05-18 22:32:00 -07:00
kunci115
2bb04f6842 test(send_message): add thread-not-found retry tests for Telegram forum topics
Adds two tests to TestSendTelegramThreadIdMapping:
- test_thread_not_found_retries_without_message_thread_id
- test_thread_not_found_for_media_retries_without_message_thread_id

Refs #27012
2026-05-18 22:32:00 -07:00
kunci115
df530b4a0c fix(send_message): add thread-not-found retry for Telegram forum topic sends
The standalone _send_telegram path in send_message_tool lacked the
thread-not-found fallback that the gateway adapter has. When a forum
topic thread_id was stale or deleted, the send would fail entirely
instead of retrying to the General topic.

Changes:
- Add _is_telegram_thread_not_found() helper matching gateway adapter
- Add thread-not-found retry in text send path
- Add thread-not-found retry in media send path (with f.seek(0))
- Separate text_kwargs from thread_kwargs to prevent
  disable_web_page_preview leaking into send_photo/send_video calls

Closes #27012
2026-05-18 22:32:00 -07:00
Teknium
fc42bb918b chore(release): map @karthikeyann for PR #26609 salvage 2026-05-18 22:30:28 -07:00
karthikeyann
ede47a54be fix(gateway): pin Telegram DM-topic routing to user's current topic
Topic-mode DM replies were fragmenting one conversation across many sessions: a Reply on a message in another topic delivered Telegram's message_thread_id for *that* topic, and #3206's strip routed plain replies to the lobby. Both pulled the user away from their current session. Fix: when topic mode is on, rewrite source.thread_id to the user's most-recent binding if the inbound id is missing/General or not a known topic. Non-topic-mode users unchanged.
2026-05-18 22:30:28 -07:00
Teknium
470edfa901 chore(release): map @aqilaziz for PR #26406 salvage 2026-05-18 22:29:45 -07:00
aqilaziz
ed9087fce7 fix(tts): keep native audio outside Telegram voice delivery 2026-05-18 22:29:45 -07:00
Teknium
e19f4c1730 chore(release): map @samahn0601 for PR #27887 salvage 2026-05-18 22:29:03 -07:00
samahn0601
af381ef12c fix(telegram): retry wrapped connect timeouts 2026-05-18 22:29:03 -07:00
Teknium
bf6a2870a7 chore(release): map @nftpoetrist for PR #25856 salvage 2026-05-18 22:28:21 -07:00
nftpoetrist
4b6d35bed2 fix(telegram): escape send_slash_confirm preview with format_message
send_slash_confirm() sent the raw command preview with ParseMode.MARKDOWN,
skipping the format_message() conversion applied to every other dynamic
send in the adapter. Commands with underscores, dots, brackets, or other
MarkdownV2-sensitive characters raised BadRequest: Can't parse entities;
the exception was swallowed by the outer try/except, so the confirmation
prompt silently never appeared.

Fix: wrap preview through format_message() and switch to MARKDOWN_V2,
symmetric with send_update_prompt and the callback sends fixed in
a69404052.
2026-05-18 22:28:21 -07:00
Teknium
35781bab90 chore(release): map @Zyrixtrex for PR #26754 salvage 2026-05-18 22:27:40 -07:00
Zyrixtrex
f8eeb570cb fix(gateway): avoid duplicate Telegram text after auto-TTS voice replies 2026-05-18 22:27:40 -07:00
Teknium
b46ef2ef7a chore(release): map @eliteworkstation94-ai for PR #28157 salvage 2026-05-18 22:25:53 -07:00
eliteworkstation94-ai
7b2bcba167 fix: avoid Telegram group reply thread session splits 2026-05-18 22:25:53 -07:00
briandevans
d69f0c1a99 fix(gateway): mark final voice reply as notify-worthy so Telegram delivers it audibly
In Telegram "important" notifications mode (default), TelegramPlatformAdapter
sets ``disable_notification=True`` on every send unless metadata carries
``notify=True``.  GatewayRunner._send_voice_reply already passes thread
metadata through to ``adapter.send_voice``, but never marks the final
auto-TTS voice reply as notify-worthy — so users with the default mode get
the final voice note delivered silently with no push notification.

Mirror the final-text path in gateway/platforms/base.py (the existing
text-response final send already adds ``metadata["notify"] = True``).

Issue #27970 Bug 2.  Bug 1 (MP3 vs. native OGG voice-note) is being
addressed by existing PRs #20182 / #20878 — this PR is intentionally
scoped to the silent-delivery bug only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:25:15 -07:00
briandevans
ba2572e54c fix(telegram): resume typing indicator after inline approval click (#27853)
The text /approve and /deny paths in gateway/run.py call
resume_typing_for_chat() after resolve_gateway_approval() succeeds, but
the Telegram inline-button (ea:*) callback in _handle_callback_query did
not. Typing is paused when the approval is sent (gateway/run.py:15658),
so without a matching resume the typing indicator stayed gone for the
remainder of a long-running turn after a button click.

Symmetry-match the text path: after a successful resolve, call
self.resume_typing_for_chat(str(query_chat_id)). Guarded by count > 0
to match /approve's "if not count" early-return — if nothing was
actually resolved, the agent thread was never unblocked, so typing
should remain paused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:20:57 -07:00
Teknium
9a444a9355 test+release: align send_message mocks for MessageEntity import; map @fonhal 2026-05-18 22:19:50 -07:00
fonhal
cf814c96f6 feat(send_message): auto-detect @username mentions and create Telegram entities
When sending messages containing @username patterns, auto-generate
MessageEntity(type='mention') entries so that the receiving bot's
require_mention filter can trigger. This enables proper bot-to-bot
interop where mention-based routing is used.
2026-05-18 22:19:50 -07:00
LeonSGP43
434d508d0a fix(telegram): propagate extra base_url config 2026-05-18 22:15:46 -07:00
Teknium
e7a3e9934f test+release: align stale sticky-IP test for #24511; map @falconexe 2026-05-18 22:14:45 -07:00
falconexe
5c4b43ced7 fix(telegram): reset sticky fallback IP on connect failure, retry primary DNS
When a sticky fallback IP (from DoH discovery) becomes unreachable,
the transport previously got stuck in an attempt_order that only
tried the dead IP.  This prevented the gateway from recovering
until the service was restarted.

Changes:
- Always include primary DNS path (None) after the sticky IP in the
  attempt_order so that a primary-path retry happens on sticky failure.
- Reset self._sticky_ip to None when the currently sticky IP hits
  a connect timeout / connect error, allowing the next request to
  retry from scratch.

Fixes silent Telegram disconnection when discovered fallback IPs
are transiently or permanently unreachable.
2026-05-18 22:14:45 -07:00
Teknium
8439ddc1b1 test(telegram): stub _is_callback_user_authorized in trigger-gating fixture
After PR #24468 made the empty-allowlist callback auth fail-closed
(and #23795 wired _is_callback_user_authorized into _should_process_message),
trigger-gating tests started failing because their fake messages from
user 111 hit the new deny-by-default path before trigger evaluation.

Force-authorize all senders in _make_adapter() so the trigger logic
under test runs.  The fail-closed behavior itself is covered by
test_telegram_callback_auth_fail_closed.py.
2026-05-18 22:08:08 -07:00
liuhao1024
89d32052ed fix(telegram): fail-closed auth fallback when TELEGRAM_ALLOWED_USERS is empty
The _is_callback_user_authorized fallback returned True when
TELEGRAM_ALLOWED_USERS was not set, allowing any Telegram user
to interact with the bot. Change to fail-closed: deny by default
unless GATEWAY_ALLOW_ALL_USERS=true is explicitly set.

Fixes #24457
2026-05-18 22:08:08 -07:00
ygd58
db50af910b fix(telegram): enforce TELEGRAM_ALLOWED_USERS allowlist on inbound messages
TELEGRAM_ALLOWED_USERS was only checked for callback/inline-button
actions but not for inbound messages. Unauthorized users triggered an
'Unauthorized user' log warning but their messages were still processed
by the agent — a P0 security bypass (issue #23778).

Fix: add allowlist check in _should_process_message() which is called
for all message types (text, command, media, location). If the sender
is not in TELEGRAM_ALLOWED_USERS, the message is dropped immediately
with a warning log. Empty TELEGRAM_ALLOWED_USERS continues to allow
all users (existing behavior).

Fixes #23778
2026-05-18 22:05:58 -07:00
Maxim Esipov
de4cb55bf3 fix(telegram): route resumed DM topic sends directly 2026-05-18 22:04:41 -07:00
Teknium
2994bf494d chore(release): map @fabiosiqueira for PR #27212 salvage 2026-05-18 22:03:12 -07:00
Fábio Siqueira
fbabd560ff fix(gateway): route background-process notifications into Telegram DM topics
Background-process completion notifications (notify_on_complete) and
watch-pattern notifications were always delivered to the Telegram main
chat instead of the originating private-chat topic.

Hermes-created Telegram DM topic lanes only render a send when it carries
both message_thread_id and a reply anchor. The synthetic MessageEvent
injected on process completion had no message_id, so _reply_anchor_for_event
returned None and _thread_kwargs_for_send dropped message_thread_id
entirely — routing the notification to the main chat.

Capture the triggering message id at spawn time and thread it through to
the synthetic event so it can be reply-anchored back into the topic:

- session_context: add HERMES_SESSION_MESSAGE_ID context var
- telegram adapter: populate SessionSource.message_id on inbound messages
- terminal tool: persist watcher_message_id on the process session
- process registry: carry/persist message_id on watcher dicts + checkpoint
- gateway: set MessageEvent.message_id on injected notifications

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:03:12 -07:00
Teknium
17f3254ede fix(test+release): update conflict retry count for MAX=5; map @CryptoByz 2026-05-18 22:01:31 -07:00
CryptoByz
f260aa6dc0 fix(telegram): recover from post-update polling conflict without entering limbo 2026-05-18 22:01:31 -07:00
Bartok9
6be579f626 fix(telegram): preserve can_edit after transient network errors in progress edits (#27828)
When edit_message_text fails with a transient error (httpx.ConnectError,
NetworkError, server disconnected, timeouts), the progress-message sender
must not permanently set can_edit = False — that would convert a single
Telegram network hiccup into separate per-tool bubbles for the rest of the run.

Changes:
- gateway/platforms/telegram.py: edit_message now returns retryable=True for
  transient network errors (ConnectError, NetworkError, timeouts, server
  disconnects, temporarily unavailable). Permanent failures (flood control,
  message-not-found, permissions) remain retryable=False.
- gateway/run.py: send_progress_messages checks result.retryable before
  setting can_edit = False. Transient failures skip the fallback-send and
  continue — the next edit cycle catches up with the accumulated lines.
  Permanent failures (flood, message-not-found, etc.) still disable editing.

Tests: 22 new tests in test_telegram_progress_edit_transient.py covering
transient vs permanent error classification, SendResult.retryable semantics,
and the can_edit decision logic.

Fixes #27828
2026-05-18 21:59:40 -07:00
Teknium
32435dfad8 chore(release): map @erhnysr for PR #25198 salvage 2026-05-18 21:58:47 -07:00
Erhnysr
1b3c51bccc fix(gateway): keep tool-progress edits alive after Telegram flood control
When a progress-message edit hits Telegram flood control (RetryAfter),
can_edit was unconditionally set to False, permanently disabling coalescing
for the rest of the run. Subsequent tool updates were posted as separate
new messages instead of updating the existing progress bubble.

Fix: only set can_edit=False for non-recoverable edit errors. On flood
control, back off by resetting _last_edit_ts so the throttle interval is
respected before the next edit attempt.

Fixes #25188
2026-05-18 21:58:47 -07:00
Teknium
256c4c1b4a fix(gateway): scope audio_file_paths outside media_urls guard
The audio-file-paths handling block at line 7334 references the variable
unconditionally, but #24879 initialized it inside the 'if event.media_urls'
block — so events without media_urls hit UnboundLocalError.

Found via test_run_agent_queued_message_does_not_treat_commentary_as_final
after PR #28478 landed.
2026-05-18 21:57:20 -07:00
Maxim Esipov
f55c67ac1f fix(gateway): roll over Telegram tool progress bubbles 2026-05-18 21:57:20 -07:00
Teknium
362ef912ea
fix(kanban-dashboard): restore implementations dropped during salvages (#28481)
Four kanban dashboard test failures, all from PR salvages that picked up
the test additions but dropped the corresponding implementations.

- BOARD_COLUMNS: add 'review' (status added by PR f55d94a1e but the
  board API never grew the column → test_board_empty failed because
  VALID_STATUSES - {archived} mismatched the rendered columns).
- update_task: enrich the 'ready' 409 detail with the blocking parent
  list (id, title, status) and add _parents_blocking_ready helper.
  Implementation lost in the #26744 salvage (commit e215558ba) which
  pinned the test but not the server-side code.
- dist/index.js: add parseApiErrorMessage helper, wire it through the
  drag/drop banner, add patchErr state to the TaskDrawer and surface
  it inline by the action row. Lost in the same #26744 salvage.
- test_diagnostics_endpoint_severity_filter: update to at-or-above
  semantics (PR a94ddd807 changed the filter from exact-match so the
  warning filter now correctly includes error+critical too).
2026-05-18 21:54:56 -07:00
Teknium
b58b4188f6 chore(release): map @pepelax for PR #25419 salvage 2026-05-18 21:54:47 -07:00
pepelax
edce8a5fd4 fix(send_message): route standalone Telegram sends through TELEGRAM_PROXY
When the send_message tool runs outside the gateway process (agent loop,
TUI, cron, etc.), _gateway_runner_ref() returns None and the standalone
path in _send_telegram constructs Bot(token=token) directly, bypassing
any configured proxy. In regions where api.telegram.org is blocked, the
send times out after ~5s with 'Telegram send failed: Timed out' and
nothing ever shows up in gateway.log because the request never reaches
the gateway.

Resolve TELEGRAM_PROXY (via gateway.platforms.base.resolve_proxy_url,
which also honours HTTPS_PROXY/HTTP_PROXY/ALL_PROXY and NO_PROXY) just
before constructing the Bot. When a proxy is found, attach an
HTTPXRequest(proxy=...) for both 'request' and 'get_updates_request',
matching what gateway/platforms/telegram.py already does for in-gateway
sends and what the Discord standalone sender already does. Any
exception attaching the proxy falls back cleanly to a direct connection,
preserving prior behaviour for users without a proxy configured.

Adds tests/tools/test_send_message_telegram_proxy.py covering both the
proxy-configured and no-proxy cases.
2026-05-18 21:54:47 -07:00
Teknium
785993bcae chore(release): map bartok9 noreply for PR #24879 salvage 2026-05-18 21:53:57 -07:00
Bartok9
b93996c35e fix(gateway): route Telegram audio file attachments away from STT pipeline (#24870)
Telegram distinguishes three kinds of audio payloads:
  - message.voice  → Opus/OGG voice messages  → STT pipeline  ✓
  - message.audio  → audio file attachments   → bypasses STT  ← was broken
  - message.document (audio mime) → generic file route

**Root cause** — the inbound message routing block in gateway/run.py
matched both MessageType.VOICE *and* MessageType.AUDIO into audio_paths,
which were then fed unconditionally to _enrich_message_with_transcription.
Audio file attachments (.mp3, .m4a, etc.) were therefore auto-transcribed
instead of being treated as files, making the transcribe skill unusable
from Telegram because the path it needed was never surfaced.

**Fix**
- Introduce a new audio_file_paths list populated exclusively by
  MessageType.AUDIO events.
- Narrow the audio_paths selector to MessageType.VOICE (and bare
  audio/ mime-type events that are not explicitly AUDIO or DOCUMENT).
- After the STT block, inject a document-style context note for each
  audio_file_path, giving the agent the file path and asking what to do
  with it (consistent with how plain documents are handled).

**Tests** — 5 new tests in test_telegram_audio_vs_voice.py:
  - voice message still transcribed (regression guard)
  - audio attachment skips STT (core fix)
  - audio attachment context note format
  - STT disabled still produces file note (not STT-disabled notice)
  - MessageType.AUDIO != MessageType.VOICE sanity check

Fixes #24870
2026-05-18 21:53:57 -07:00