Skip to content

feat(telegram): edit status messages in place instead of appending - #30864

Merged
teknium1 merged 1 commit into
mainfrom
feat/telegram-status-edit
May 23, 2026
Merged

feat(telegram): edit status messages in place instead of appending#30864
teknium1 merged 1 commit into
mainfrom
feat/telegram-status-edit

Conversation

@teknium1

@teknium1 teknium1 commented May 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Telegram status callbacks now edit one message in place instead of appending a new bubble on every emit (closes #30045).

Based on @qike-ms's PR #30141 — same feature, slimmer implementation.

Changes

  • gateway/platforms/telegram.py (+38): send_or_update_status(chat_id, status_key, content, *, metadata) method backed by a {(chat_id, status_key) -> message_id} dict. Edits if cached, sends if not, drops the cache entry and re-sends on edit failure. Uses the existing send() and edit_message() paths (overflow splitting, MarkdownV2 formatting, notification kwargs all inherited).
  • gateway/run.py (+19): _status_callback_sync now routes through a small module-level helper that prefers the adapter's send_or_update_status when present, falls back to plain adapter.send() otherwise. No other status-emitting surfaces touched.
  • tests/gateway/test_telegram_status_update.py (+155): 5 tests — first send caches id, second call edits, edit failure falls back to fresh send and refreshes cache, distinct status_key doesn't collide, distinct chat_id doesn't collide.

Validation

Before After
Status callbacks on Telegram New bubble per emit One bubble, edited in place
Edit failure (message deleted / too old) n/a Cache cleared, fresh send
Adapters without the method (Discord, Slack, …) adapter.send() adapter.send() (unchanged)
Targeted tests 5 new, 29 total green (status + transient + run-progress-interrupt)

Credit

Issue #30045 and the original implementation were authored by @qike-ms in PR #30141. This PR keeps his feature shape (send_or_update_status(chat_id, status_key, text), edit-or-send semantics, fallback to fresh send on edit failure) and drops the duplicated UTF-16 truncation helpers, the duck-typed adapter capability checks, the OrderedDict LRU + per-key asyncio.Lock dict — the underlying send() / edit_message() paths already handle length, formatting, and overflow splitting, so the wrapper only needs the cache.

Infographic

telegram-status-edit

Closes #30045. Based on @qike-ms's PR #30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: feat/telegram-status-edit vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9024 on HEAD, 9002 on base (🆕 +22)

🆕 New issues (22):

Rule Count
unresolved-attribute 19
invalid-assignment 2
unresolved-import 1
First entries
tests/gateway/test_telegram_status_update.py:29: [unresolved-attribute] unresolved-attribute: Unresolved attribute `InlineKeyboardButton` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:39: [unresolved-attribute] unresolved-attribute: Unresolved attribute `ParseMode` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:73: [invalid-assignment] invalid-assignment: Object of type `AsyncMock` is not assignable to attribute `edit_message` of type `def edit_message(self, chat_id: str, message_id: str, content: str, *, finalize: bool = False, metadata: dict[str, Any] | None = None) -> CoroutineType[Any, Any, SendResult]`
tests/gateway/test_telegram_status_update.py:28: [unresolved-attribute] unresolved-attribute: Unresolved attribute `Message` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:26: [unresolved-attribute] unresolved-attribute: Unresolved attribute `Update` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:44: [unresolved-attribute] unresolved-attribute: Unresolved attribute `constants` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:36: [unresolved-attribute] unresolved-attribute: Unresolved attribute `error` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:50: [unresolved-attribute] unresolved-attribute: Unresolved attribute `MessageHandler` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:51: [unresolved-attribute] unresolved-attribute: Unresolved attribute `ContextTypes` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:47: [unresolved-attribute] unresolved-attribute: Unresolved attribute `Application` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:72: [invalid-assignment] invalid-assignment: Object of type `AsyncMock` is not assignable to attribute `send` of type `def send(self, chat_id: str, content: str, reply_to: str | None = None, metadata: dict[str, Any] | None = None) -> CoroutineType[Any, Any, SendResult]`
tests/gateway/test_telegram_status_update.py:52: [unresolved-attribute] unresolved-attribute: Unresolved attribute `filters` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:49: [unresolved-attribute] unresolved-attribute: Unresolved attribute `CallbackQueryHandler` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:40: [unresolved-attribute] unresolved-attribute: Unresolved attribute `ChatType` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:17: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_status_update.py:33: [unresolved-attribute] unresolved-attribute: Unresolved attribute `NetworkError` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:27: [unresolved-attribute] unresolved-attribute: Unresolved attribute `Bot` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:35: [unresolved-attribute] unresolved-attribute: Unresolved attribute `TimedOut` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:55: [unresolved-attribute] unresolved-attribute: Unresolved attribute `HTTPXRequest` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:30: [unresolved-attribute] unresolved-attribute: Unresolved attribute `InlineKeyboardMarkup` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:34: [unresolved-attribute] unresolved-attribute: Unresolved attribute `BadRequest` on type `ModuleType`
tests/gateway/test_telegram_status_update.py:48: [unresolved-attribute] unresolved-attribute: Unresolved attribute `CommandHandler` on type `ModuleType`

✅ Fixed issues: none

Unchanged: 4780 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1
teknium1 merged commit 9acf949 into main May 23, 2026
23 of 24 checks passed
@teknium1
teknium1 deleted the feat/telegram-status-edit branch May 23, 2026 09:42
@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter labels May 23, 2026
Gpapas pushed a commit to Gpapas/hermes-agent that referenced this pull request May 23, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
Mucky010 pushed a commit to Mucky010/hermes-agent that referenced this pull request May 24, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
mathias3 pushed a commit to mathias3/hermes-agent that referenced this pull request May 28, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
Bryce-huang pushed a commit to wbkunlun/hermes-agent that referenced this pull request May 29, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
#AI commit#
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
Seven74AI pushed a commit to Seven74AI/hermes-agent that referenced this pull request Jun 13, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
alt-glitch pushed a commit that referenced this pull request Jun 14, 2026
…30864)

Closes #30045. Based on @qike-ms's PR #30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
liuchanchen pushed a commit to liuchanchen/hermes-agent that referenced this pull request Jun 23, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
donbowman pushed a commit to donbowman/hermes-agent that referenced this pull request Jul 13, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…ousResearch#30864)

Closes NousResearch#30045. Based on @qike-ms's PR NousResearch#30141.

Telegram status callbacks (lifecycle, compression, context-pressure)
used to append a fresh bubble on every emit. Now adapter tracks
{(chat_id, status_key) -> message_id}; first call sends, subsequent
calls edit. Failed edits drop the cache entry and fall through to a
fresh send.

- gateway/platforms/telegram.py: send_or_update_status() (+34 LOC)
- gateway/run.py: route _status_callback_sync through it when the
  adapter supports it; plain adapter.send() otherwise (+15 LOC)
- 5 tests covering first send / edit-in-place / edit-failure fallback
  / distinct key & chat isolation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: update Telegram status messages instead of appending progress spam

2 participants