Repository navigation
Conversation
Contributor
Author
ideshun
marked this pull request as ready for review
October 15, 2026 16:16
Bosheng0422
reviewed
Oct 16, 2026
Bosheng0422
left a comment
Collaborator
There was a problem hiding this comment.
Two issues should be fixed before merge. Button actions stall Telegram polling for the whole agent turn, and topic isolation rewrites private-chat and non-topic session keys.
Telegram 无表格语法, GFM 竖线表格在比例字体下排版错乱. 发送前把表格重排 CJK 双宽感知的列对齐代码块, 输出 <pre> 等宽渲染.
QQ weixin pingdao wu kekao dengkuan xuanyan, biao ge jiang wei jian-zhi lie-biao xing; fuyong telegram bu ding de biao ge jie xi (renderer can shu hua), chuai dan ce 6 xiang
GeneratorExit chuan guo async-for bu hui guan bi nei ceng sheng cheng qi; ge ceng bu finally-aclose hou, zheng tiao liu jiao gei GC zai yi yuan Context qing li, zhi octop_hitl_thread/octop_turn_model reset beng kui, resume zhong tu diu shi (dian ji an niu wu fan ying cheng yin zhi yi). jia gu liang ge scope de kua Context reset; an niu yan zheng shi bai luo WARNING yin gu. hui gui ce shi 3 xiang.
…allback GFM tables ride Bot API 10.1 sendRichMessage (rich_message.markdown) for native client rendering; any failure falls back to the aligned code-block conversion.
toast + settle keyboard after choice; tests 4
close_event_stream comes from gateway 1.0.3; a local fallback keeps installs on older resolver versions working.
Co-authored-by: Cursor <cursoragent@cursor.com>
…h tables The rich-table path edited the group preview placeholder in place but skipped the preview bookkeeping that the gateway's _finish_preview does. The message id stayed in the stream state, so the next segment of the same turn kept editing that message and overwrote the rendered table. sendRichMessage also dropped message_thread_id, reply parameters and the silent/protect-content options, which put forum-topic tables in General. - Edit in place only when the preview is finalizing and its text matches, then release message_id/finalizing/stop token like _finish_preview. - Send through the gateway request scheduler with _send_options so rate limiting, 429 retries, topics and reply threading apply. - Fall back to the aligned code block only on explicit rejections (BadRequest, InvalidToken from older Bot API servers). Ambiguous network errors mark delivery_failed and are not resent. Tests: tests/unit/gateway/test_rich_table_delivery.py covers in-place edit, state release, non-finalizing previews, topic/reply options, fallback and the no-resend rule. Co-authored-by: Cursor <cursoragent@cursor.com>
Rejected-button notices and the quoted-message prompt prefix are host generated copy and belong in the backend bundles, not hard-coded in infra/. - channel.telegram.reject.* (with a default) and channel.telegram.quote.* in en.json and zh.json. - telegram_reject_notice() and telegram_quote_prompt() in i18n/domains/channel.py; unknown reasons use the default notice. Co-authored-by: Cursor <cursoragent@cursor.com>
Reply quotes - The gateway only forwards reply_to_message_id. Wrap _extract_message (async, kept async) to add reply_to_text/reply_quote_text and parse_inbound (sync) to pass exactly those two keys through; the whole payload is not passed through because that leaked `content` into metadata. Quotes are capped at 2000 characters and a reply to the bot's own message is not injected, since it is already in the history. - The bridge prefixes the prompt with the localized quote header. Mentions - Strip only this bot's own @username (taken from the channel). Other mentions, other bots, e-mail addresses and words such as @Robot stay. Indentation and repeated spaces in the message are left untouched. Choice buttons - In group chats the thread binding moves with every new message, so the thread identity check made still-valid taps fail silently. Group buttons now rely on the gateway's user+chat check, owner/model checks and, for HITL, the pending record (session + user + question index). Private chats keep the strict thread checks. - Rejected taps send a localized notice instead of being dropped. - The text card is held back while native buttons are sent and is released again if sending the buttons fails. - Buttons pass ttl=1800 to send_buttons (aligned with the 30 minute HITL record) instead of patching Interactions.register. - Interactions.invoke ignores the topic for callbacks registered without one; user and chat are still checked. Mismatches are logged at INFO to confirm why the topics differ. All local channel patches are installed from patch_entry.install(). Tests: test_group_chat_regressions.py plus adjusted bridge and stream-close tests.
…liveness The local patch layer wraps private gateway methods. A sync wrapper around an async method once made every inbound Telegram message go unanswered, and a gateway rename would disable a patch without any signal. - Every wrapper records __wrapped_base__; the audit discovers them and checks that coroutine-ness matches the replaced method and that the wrapper accepts all of the base method's parameters. - patch_entry.verify() checks that each patch target still exists and is wrapped; install() logs a warning naming the inactive patches. - Clean up the mypy ignores in the Telegram patch modules. Co-authored-by: Cursor <cursoragent@cursor.com>
User-facing behavior changes from the Telegram group chat fixes: rich tables, reply quotes, own-bot mention stripping and choice buttons. Co-authored-by: Cursor <cursoragent@cursor.com>
…import With octop-gateway>=1.0.3 pinned, the imported close_event_stream matches the fallback signature, so mypy reports the assignment ignore as unused and `make typecheck` fails on Linux and Windows. Co-authored-by: Cursor <cursoragent@cursor.com>
The bridge wrapper was dropped from Gateway._register_channel, so native choice buttons, own-bot mention stripping, quote injection and button rejection notices never ran. Restore the wrap (once, idempotent) and cover it with a registration test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

已拆分并由以下 PR 替代:
原分支及历史讨论保留。以下为原 PR 说明。
Behavior
Expose Telegram group activation, allowlists, topic isolation, reply/edited-message policy, streaming, reactions, delivery options and media settings in the channel drawer. Defaults preserve existing group activation; users can explicitly choose stricter modes.
Topic-isolated sessions include channel instance, chat and topic identity, with routing metadata retained for scheduled delivery. Single-choice
ask_user_questionprompts and paginated model menus use native callbacks bound to sender/chat/topic and recheck agent ownership, thread identity and model eligibility. Stopping a Telegram response also signals the active Harness stream.Dependency and scope
Requires
octop-gateway>=1.0.3(companion PR octop-gateway#34, released in 1.0.3); the pin is part of this PR. Channel save/probe rejects unsupported options on older Gateway builds instead of silently ignoring them.Agent switching is explicitly unsupported. Multi-select/free-text questions retain text handling. This does not introduce Telegram security-approval buttons or grant group administrators Octop administrative authority.
Validation
pytest -m "not live"on Python 3.12 and Windows,make lint,make typecheck).ruff,mypy src/octop,tests/unit/gateway+tests/unit/i18n(694 passed), dashboardtscand the Telegram channel tests.ask_user_questionchoice buttons.Channel messaging improvements (before → after)
1. Choice cards: tap feedback & keyboard settlement
Before
/modeladditionally pushed two identical menu messages (the bridge processor was wrapped twice), and expired choices kept live-looking buttons forever (zombie buttons).After
✓ <label>at the top and settles the original message: the selection is appended and the inline keyboard is removed.Stopkeeps its existing streaming cleanup path./modelinvocation.2. Table rendering across channels
Before
| column | column |lines with a|---|---|separator row — unreadable in proportional fonts (scrambled columns, noisy separator).After
sendRichMessage(rich_message.markdown) for native table rendering (real borders, column alignment, up to 20 columns). Any failure — older client, API rejection — falls back to a CJK-double-width-aware column-aligned code block.- 名称=CPU | 数值=80%) — the only layout that survives proportional fonts and line wrapping.Group chat reliability and review follow-ups
Fixes found while using the Telegram integration in group chats. They only touch the Telegram channel and its local gateway patches; nothing here changes plugin, dashboard or non-Telegram behavior.
Behavior
Rich tables (
telegram_table_format.py)sendRichMessagealso droppedmessage_thread_id, reply parameters and the silent/protect-content options, so forum-topic tables landed in General. Any exception fell back to a resend, including ambiguous timeouts._finish_previewdoes. Rich messages go through the gateway request scheduler with_send_options. Only explicit rejections (BadRequest,InvalidTokenfrom older Bot API servers) fall back to the aligned code block; network errors setdelivery_failedand are not resent.Reply quotes (
telegram_reply_quote.py)reply_to_message_id, so the agent never saw what the user replied to._extract_message(async) now addsreply_to_text/reply_quote_text, andparse_inbound(sync) passes exactly those two keys through. Passing every unknown payload key through leakedcontentinto metadata, so the list is explicit.Mentions (
telegram_bridge.py)@usernameis removed, using the username the channel already knows. Other bots, e-mail addresses (john@abbot.com) and words such as@robotare kept. Indentation and repeated spaces are left untouched; the previous whitespace folding stripped indentation from every message. Without a known username nothing is stripped.Choice buttons
invoke, so a toast is not possible.ttl=1800tosend_buttons(aligned with the 30 minute HITL record); the previousInteractions.registerpatch is gone.Interactions.invokeignores the topic for callbacks registered without one. User and chat are still checked. A mismatch is logged at INFO (see Open question).Bridge wiring (
gateway.py)Gateway._register_channelwraps Telegram channels inTelegramBridgeonce. An earlier commit in this branch had dropped the wrap, so native choice buttons, reject notices and the bridge-level mention/quote handling silently did not run (only the plain text card reached the user).test_telegram_bridge_wiring.pyregisters a channel through_register_channeland fails without the wrap.Patch layer
patch_entry.install().test_patch_audit.pyfinds every wrapper through__wrapped_base__and checks that coroutine-ness matches the replaced method and that the wrapper accepts all of its parameters.patch_entry.verify()logs a warning when a patch target is missing or no longer wrapped, so a gateway upgrade cannot disable a patch silently._extract_messageonce made every inbound Telegram message go unanswered (coroutine was never awaited).i18n
channel.telegram.*inen.json/zh.jsoninstead of being hard-coded ininfra/. Users without a stored locale get the channel default (enfor Telegram).Gateway follow-ups
message_thread_idmeans "message thread or forum topic" in the Bot API; onlyis_topic_messagetells them apart. Confirmed on a live non-forum supergroup: buttons were registered with topicNone(user message) while the tap carried the bot message's thread id (registered=None actual=397). Theinvokepatch keeps taps working until the gateway fix ships; once it does, the patch and its two diagnostic logs can be removed.is_topic_message).ContextVar ... created in a different Contextafter eachask_user_questionpause. Log noise only, pre-existing, not addressed here.Validation
ruff check,ruff format --checkandmypyonsrc/octop/infra/gatewayandsrc/octop/i18n: clean.tests/unit/gateway+tests/unit/i18n: 694 passed against octop-gateway 1.0.3, octop-harness 1.0.4, octop-memory 1.0.2.constants.test.ts(11 tests) andtsc --noEmitpass after mergingdevelop.test_rich_table_delivery.py,test_group_chat_regressions.py,test_patch_audit.py,tests/unit/i18n/test_channel.py.Screenshots
Before/after screenshots are in the sections above and in the PR comment (channel configuration UI).
Checklist
developCHANGELOG.md