Commit graph

4007 commits

Author SHA1 Message Date
MoonJuhan
b8a9cbd18c fix: tolerate unreadable gateway JSONL transcripts 2026-05-19 00:11:12 -07:00
outsourc-e
663ee14865 fix(cron): allow emoji ZWJ sequences in prompts 2026-05-19 00:10:43 -07:00
noctilust
425aba766b fix(cli): ignore stale HERMES_TUI_RESUME env
HERMES_TUI_RESUME is an internal env var the Python wrapper exports to hand
a session ID off to the Ink TUI. Because _launch_tui started from
os.environ.copy(), any exported/stale value in the user's shell leaked
through — so plain `hermes --tui` would try to resume a missing session
and leave the UI at 'error: session not found' with no live session.

Drop HERMES_TUI_RESUME from the env before conditionally re-setting it
from the argparse-resolved resume_session_id. Tests cover both the drop
path and the set-from-arg path.

Salvage of #28080 by @noctilust.
2026-05-19 00:10:15 -07:00
YuanHanzhong
afffb8d9a5 fix(dashboard): use browser scrollback for chat wheel 2026-05-19 00:07:33 -07:00
LifeJiggy
0b89628e86 test(file_ops): add regression tests for git baseline warning in write_file
Adds TestGitBaselineCheck with 6 unit tests covering _check_git_baseline
and the warning field in write_file result:
- Git not available → None
- Not in a git repo → None
- Clean repo → None
- Dirty repo → returns warning string with branch name
- write_file result includes warning when dirty
- write_file result omits warning when clean
2026-05-19 00:06:55 -07:00
Teknium
22120ef00f Revert "feat(telegram): support quick-command-only menus"
This reverts commit b1acf80e17.
2026-05-18 23:59:57 -07:00
Albert G
ad2531be08 feat(telegram): skip-STT audio path + 2GB cap via local Bot API server
Two coordinated changes that unblock downstream audio pipelines
(diarization, custom transcription, archival) on attachments larger
than the public Bot API's 20MB getFile ceiling.

- `stt.enabled: false` no longer drops voice/audio with a generic
  "transcription disabled" note. The gateway probes the cached file's
  duration (wave → mutagen → ffprobe ladder) and surfaces
  `[The user sent a voice message: <abs path> (duration: M:SS)]` to
  the agent so a skill or tool can pick up the raw file. The previous
  placeholder is replaced rather than appended when present.

- `platforms.telegram.extra.base_url` set → adapter auto-lifts its
  document size cap from 20MB to 2GB (the local telegram-bot-api
  `--local` ceiling) and the "too large" reply reports the active
  limit dynamically. No new config knob; presence of `base_url` is the
  opt-in.

- `platforms.telegram.extra.local_mode: true` wires
  `Application.builder().local_mode(True)` on the python-telegram-bot
  builder. PTB then reads files from disk instead of HTTP, which is
  required when telegram-bot-api runs in `--local` mode (the server
  returns absolute filesystem paths, not `/file/bot...` URLs).

- gateway/run.py: rewrites the `stt.enabled: false` branch of
  `_enrich_message_with_transcription`. New `_format_duration` +
  `_probe_audio_duration` helpers.
- gateway/platforms/telegram.py: `_max_doc_bytes` instance attribute
  derived from `extra.base_url`; `local_mode` builder wiring;
  dynamic "too large" message.
- tests/gateway/test_stt_config.py: covers path-surfacing with and
  without an existing user message, and placeholder replacement.
- tests/gateway/test_telegram_max_doc_bytes.py: 3 cases — default 20MB
  without base_url, 2GB when set, empty-string base_url keeps default.
- website/docs/user-guide/messaging/telegram.md: new "Skipping STT"
  subsection under Voice Messages and a full "Large Files (>20MB) via
  Local Bot API Server" walkthrough (api_id/api_hash, docker-compose,
  one-time `logOut` migration, `platforms.telegram.extra` config, the
  `local_mode` disk-access requirement, the silent HTTP-fallback 404).
- website/docs/user-guide/features/voice-mode.md: documents the
  `stt.enabled` knob in the config reference.

- `pytest tests/gateway/test_telegram_max_doc_bytes.py
  tests/gateway/test_stt_config.py` → 9/9 passing.
- Verified end-to-end on a live deployment: gateway log shows
  `Using custom Telegram base_url: http://...` and
  `Using Telegram local_mode (read files from disk)` on startup;
  voice messages above 20MB cache to disk and surface their path to
  the agent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 22:59:40 -07:00
William Chen
fbfe294882 fix: ignore Telegram messages for other bots 2026-05-18 22:54:15 -07:00
William Chen
ce4d857021 Route Telegram multi-bot mentions exclusively 2026-05-18 22:54:15 -07:00
Bob Yang
8a80eee02d Quiet noisy Telegram gateway errors 2026-05-18 22:53:01 -07:00
Teknium
f1cefad8c2 test+release: stub auth in channel_posts fixture; map @brndnsvr 2026-05-18 22:51:35 -07:00
Brandon Seaver
84a9b81502 test: address telegram channel post review 2026-05-18 22:51:35 -07:00
Brandon Seaver
704872a62f fix(telegram): handle channel post updates 2026-05-18 22:51:35 -07:00
stevehq26-bot
b1acf80e17 feat(telegram): support quick-command-only menus 2026-05-18 22:48:42 -07:00
analista
d81b888807 fix(telegram): report cron topic fallback 2026-05-18 22:45:05 -07:00
fonhal
16d8e44f7a fix(telegram): add DM topic typing fallback when message_thread_id rejected
When a DM topic lane's message_thread_id is rejected by Telegram
(e.g. stale or deleted topic), send_typing now falls back to sending
the typing indicator without thread_id so it at least appears in the
main DM view, rather than being silently swallowed.

Also adds test for the fallback behavior.
2026-05-18 22:43:46 -07:00
soynchux
b38140eb8f fix(gateway): allow chat-scoped telegram auth without sender user_id 2026-05-18 22:43:14 -07:00
JackJin
95a0955e19 fix(gateway): restore Telegram DM topic thread_id after session split (#27166)
When context compression triggers a mid-turn session split, source.thread_id
can be None on synthetic/recovered events. _thread_metadata_for_source then
returns None, causing the Telegram adapter to send with no message_thread_id
and the response lands in the General thread instead of the active DM topic.

Fix:
- hermes_state.py: Add get_telegram_topic_binding_by_session() for reverse
  lookup by session_id (enabled by the existing UNIQUE INDEX on session_id).
- gateway/run.py: After session-split detection, if source is a Telegram DM
  and source.thread_id is None, recover it from the binding via the new
  method so _thread_metadata_for_source produces the correct thread routing.
- tests/: Coverage for the new lookup method and the recovery flow.
2026-05-18 22:42:28 -07:00
B0Tch1
9d789f3a5b feat(telegram): add disable_topic_auto_rename gateway flag
When Hermes auto-titles a session in a Telegram DM topic it currently
renames the topic itself to the generated title. That works for
operator-managed lanes (extra.dm_topics) but is disruptive for
ad-hoc Threaded-Mode topics that users name by hand — every first
exchange overwrites their chosen title.

Add gateway.platforms.telegram.extra.disable_topic_auto_rename (default
False, preserving prior behaviour). When set, both
_schedule_telegram_topic_title_rename and the underlying
_rename_telegram_topic_for_session_title short-circuit before touching
the Telegram API. Internal session titles (sessions list, TUI) keep
working unchanged.

Also bridge the legacy top-level telegram.disable_topic_auto_rename key
through to gateway.platforms.telegram.extra so users on the older
config layout don't have to migrate to enable it.

- Tests cover the runtime flag, the scheduling entry-point, and string
  truthiness coercion for YAML-loaded values.
- Docs updated in messaging/telegram.md with an example block.
2026-05-18 22:40:44 -07:00
Maxim Esipov
3ec28f34ca fix(telegram): preserve topic metadata on overflow edits 2026-05-18 22:40:03 -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
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
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
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
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
aqilaziz
ed9087fce7 fix(tts): keep native audio outside Telegram voice delivery 2026-05-18 22:29:45 -07:00
samahn0601
af381ef12c fix(telegram): retry wrapped connect timeouts 2026-05-18 22:29:03 -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
Zyrixtrex
f8eeb570cb fix(gateway): avoid duplicate Telegram text after auto-TTS voice replies 2026-05-18 22:27:40 -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
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
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
Maxim Esipov
de4cb55bf3 fix(telegram): route resumed DM topic sends directly 2026-05-18 22:04:41 -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
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
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
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
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
liuhao1024
21a15b6711 fix(telegram): respect reply_to_mode for DM topic reply fallback
The DM topic reply fallback code in send() hardcoded should_thread=True
when telegram_dm_topic_reply_fallback metadata was present, bypassing
_should_thread_reply() and ignoring reply_to_mode config. This caused
quote bubbles on every response even with reply_to_mode: 'off'.

Fix:
- Add reply_to_mode param to _reply_to_message_id_for_send() and
  _thread_kwargs_for_send() classmethods
- In send(), check self._reply_to_mode != 'off' for DM topic fallback
- Suppress reply anchor and reply_to_message_id when mode is 'off'
  while preserving message_thread_id for correct topic routing
- Thread reply_to_mode through all 29 call sites

Regression coverage: 10 new tests in test_telegram_reply_mode.py
covering classmethod behavior, send() integration, and backward
compatibility.

Fixes reply_to_mode: 'off' ignored by Telegram DM topic reply fallback code #23994
2026-05-18 21:52:39 -07:00