Commit Graph
126 Commits
Author SHA1 Message Date
chengyongruandXubin Ren f38fd7d5d3 feat(memory): add workspace Dream prompt override 2026-07-03 00:41:51 +08:00
chengyongruandXubin Ren b690a48336 fix(trigger): require names for trigger creation 2026-07-02 13:32:46 +08:00
chengyongruandXubin Ren 2a0cd19a74 feat(trigger): add session-bound local triggers 2026-07-02 13:32:46 +08:00
chengyongruandXubin Ren 5f4cfbcb16 refactor(bus): type outbound runtime events 2026-07-01 20:17:00 +08:00
chengyongruandXubin Ren e92899607a fix: make Telegram rich messages opt in
maintainer edit: Telegram Web cannot render sendRichMessage payloads, so keep the rich path available only for explicit opt-in instead of enabling it by default.
2026-06-25 16:10:33 +08:00
axelray-devandXubin Ren c930aa3713 fix: add rich_messages config to disable sendRichMessage for Telegram Web (#4488) 2026-06-25 16:10:33 +08:00
axelray-devandXubin Ren 160cec2396 fix: skip sendRichMessage when streaming preview exists (#4470)
When _stream_end fires and a streaming preview already exists, the
sendRichMessage path deletes the preview and sends a fresh message.
This causes line break loss and visible flickering. Gate the rich
path on not buf.message_id so existing previews use the legacy
edit_message_text path instead.
2026-06-24 10:19:14 +08:00
NanoBotandXubin Ren a8c65b50ca fix: narrow rich capability error detection to prevent false latch
- Remove overly broad 'not found' match that could trigger on transient
  errors like 'chat not found' or 'message to reply not found'
- Keep only 'method not found' and 'unknown method' which indicate
  the server genuinely doesn't support sendRichMessage
- Demote timeout log from warning to debug since it falls back to
  legacy path successfully
2026-06-21 15:22:05 +08:00
NanoBotandXubin Ren e9494c1dce feat(telegram): add Bot API 10.1 sendRichMessage support
- Try sendRichMessage for all non-blockquote messages (tables, task lists,
  math, collapsible, etc.) with graceful fallback to legacy HTML
- Fix payload format to use top-level 'markdown' field per Bot API 10.1 spec
- Add rich path in send_delta() stream end (delete preview + send rich)
- Latch off permanently if server returns capability error
- Removes _needs_rich_rendering() gate and 4 regex patterns
2026-06-21 15:22:05 +08:00
chengyongruandXubin Ren ffae1dca6d fix: keep Telegram streamed code blocks balanced
Maintainer edit: split final streamed Telegram markdown before rendering to HTML so long fenced code blocks do not produce unbalanced <pre><code> chunks while still respecting Telegram's rendered HTML limit.
2026-06-11 13:52:19 +08:00
axelray-devandXubin Ren a5a816abaf fix(telegram): move fenced-code-block splitting into Telegram-specific helper
Move the fenced-code-block-aware splitting logic out of the shared
split_message helper (used by Signal, Slack, Discord, Weixin, etc.)
and into a Telegram-specific _split_telegram_markdown function.

The shared split_message remains a plain-text chunker. The Telegram
channel now uses _split_telegram_markdown for its raw Markdown paths
that feed _markdown_to_telegram_html, preventing broken HTML rendering
when splits fall inside fenced code blocks.

Also fixes a regression where content beginning with whitespace before
a fence could emit a whitespace-only chunk.

Addresses review feedback on #4257.
2026-06-11 13:52:19 +08:00
Xubin Ren 6a0368b32f fix(telegram): route /skill command 2026-06-05 18:48:51 +08:00
chengyongruandXubin Ren 3da68ac7fe Fix pairing for Weixin and Telegram DMs 2026-06-05 16:13:31 +08:00
outlook84andXubin Ren a4a2c55120 feat(telegram): add webhook support and ordered message queue
Introduce webhook mode for the Telegram channel and implement a session-based message reordering mechanism.

    Key changes:
    - Update `python-telegram-bot` dependency to include the `webhooks` extra.
    - Add `TelegramConfig` fields for webhook configuration, with validation rules for public HTTPS URLs and Telegram's secret token.
    - Implement `_enqueue_ordered_update` and `_drain_ordered_updates` in `TelegramChannel` to stage incoming messages and commands behind a short per-session reorder
  window, ensuring sequential delivery based on message and update IDs.
    - Configure `start_webhook` in `TelegramChannel.start()` when webhook mode is enabled.
    - Add unit tests for webhook config validations, webhook startup, and message reordering.
    - Document webhook configuration and reverse proxy details in `docs/chat-apps.md`.
2026-05-26 16:14:51 +08:00
Xubin Ren 9ccef018c2 feat(telegram): add new slash commands and update regex for command handling 2026-05-15 17:55:52 +00:00
chengyongruandXubin Ren 4c4a9ae590 feat(pairing): chat-native DM sender approval
Replace the file-editing onboarding workflow with a chat-native pairing flow:

- New pairing store (nanobot/pairing/store.py) persists approved senders
  and pending codes in ~/.nanobot/pairing.json.
- DM messages from unknown senders receive a short pairing code instead of
  silent denial. Group chats remain silently ignored.
- Existing allowFrom semantics are fully preserved; approved pairing users
  are merged at runtime so no config migration is needed.
- nanobot pairing list/approve/deny/revoke CLI commands for bootstrap and
  emergency management.
- /pairing slash commands intercepted in-channel so owners can approve
  senders without leaving the chat.
- is_dm flag added to BaseChannel._handle_message; Telegram, Discord and
  WebSocket updated to pass it.

Closes #3768
2026-05-15 15:46:44 +08:00
chengyongruandXubin Ren 05e0106592 refactor(logging): preserve tracebacks and add channel context
- Preserve tracebacks: logger.error in except blocks → logger.exception
- Channel context: BaseChannel injects self.logger = logger.bind(channel=name)
- Third-party bridge: redirect_lib_logging() replaces ad-hoc stdlib-to-loguru bridges
- Log levels: network timeouts downgraded from ERROR → WARNING
- Fix --verbose flag to actually work with loguru (set handler to DEBUG)
2026-05-06 21:17:45 +08:00
4db50f2e32 fix(channels): reject unauthorized inbound before side effects
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 23:16:36 +08:00
DG MulticaandXubin Ren 5aa61e08d3 fix(telegram): ignore unauthorized users silently 2026-05-05 23:16:36 +08:00
Jack LuandXubin Ren d9800ecdd2 refactor: replace try-except blocks with contextlib.suppress for cleaner error handling across multiple files 2026-05-01 19:30:11 +08:00
SimonandXubin Ren e36e70fe16 fix(channels): send telegram attachments with named file path 2026-04-28 15:13:49 +08:00
Xubin RenandXubin Ren 2b886ffd1f fix(command): expose history in chat command menus
Made-with: Cursor
2026-04-27 18:23:35 +08:00
Xubin RenandXubin Ren be05189f39 feat(channels): add video support for Telegram and WebSocket
Telegram previously sent all video files as documents via send_document,
so users saw a file icon instead of an inline player. WebSocket only
accepted image MIME types, rejecting video uploads entirely.

Telegram:
- Recognize video extensions (mp4/mov/avi/mkv/webm/3gp) in _get_media_type
- Route videos through send_video with supports_streaming=True
- Add VIDEO/VIDEO_NOTE/ANIMATION to inbound message filters
- Add video MIME mappings to _get_extension
- Fix: local file sends now use _call_with_retry (previously no retry)

WebSocket:
- Expand upload MIME whitelist with video/mp4, video/webm, video/quicktime
- Add per-type size limits (_MAX_VIDEO_BYTES=20MB, _MAX_VIDEOS_PER_MESSAGE=1)
- Expand media serving endpoint to serve video with correct Content-Type

Agent:
- Add "video" to message tool media parameter description
- Add .mp4 example to identity.md system prompt

Made-with: Cursor
2026-04-25 02:20:13 +08:00
Xubin RenandXubin Ren 06503cd0fc fix(telegram): keep callback_data under Telegram's 64-byte cap
``InlineKeyboardButton(label, callback_data=label)`` fails Telegram's
API when the label exceeds 64 bytes UTF-8. An LLM-generated long
option (realistic in multilingual flows) used to 400 the ``send_message``
call silently — user got nothing, agent heard a successful retry-then-drop.

Decouple display from wire: button text keeps the full label, callback_data
gets truncated at a UTF-8 char boundary. Tap echoes the prefix back as the
user message; the LLM understands a prefix of its own option just fine,
and the display the user saw was always the full string.

Locks: helper boundary behavior (ASCII, CJK, short labels pass through)
and end-to-end ``_build_keyboard`` integration with an over-cap label.

Made-with: Cursor
2026-04-23 13:26:06 +08:00
Xubin RenandXubin Ren 6bc2983ab1 fix(telegram): fall back buttons to inline text when keyboard disabled
Buttons are semantic options, not a separate channel protocol: a user
who taps "Yes" and a user who types "yes" arrive at the agent as the
same string. Dropping ``msg.buttons`` when ``inline_keyboards=False``
was the worst of both worlds — the agent got told "Message sent with
N button(s)" while the user saw a question with no options.

Splice the labels into the message text instead. The LLM produces the
same ``message(buttons=...)`` call regardless of channel; the channel
layer picks the richest rendering it can afford — native keyboard when
enabled, bracketed inline text otherwise. Layout is preserved (one row
per line). Other channels can adopt the same helper incrementally.

Locks: canonical ``_buttons_as_text`` format, flag-off send-path
splices labels, flag-on send-path keeps content clean and rides
``reply_markup``.

Made-with: Cursor
2026-04-23 13:26:06 +08:00
Gunnar ThielebeinandXubin Ren 8d33c1cb37 feat(telegram): add inline keyboard buttons 2026-04-23 13:26:06 +08:00
hussein1362andXubin Ren f8a023218d fix(telegram): improve markdown rendering for modern LLM output
Problem:
Modern LLMs (GPT-5.4, Claude, Gemini) produce markdown-heavy responses with
numbered lists, headers, and nested formatting. The Telegram channel's
_markdown_to_telegram_html() converter has gaps that leave these poorly
formatted:

1. Numbered lists (1. 2. 3.) have zero handling — sent as raw text
2. Headers (# Title) are stripped to plain text, losing visual hierarchy
3. Mid-stream edits send raw markdown (users see **bold** and ### headers
   while the response generates, before the final HTML conversion)

Root Cause:
_markdown_to_telegram_html() handles bullets (- *) but skips numbered lists
entirely. Headers are stripped of # but not given any emphasis. The streaming
path in send_delta() sends buf.text as-is during mid-stream edits (plain
text, no parse_mode) — only the final _stream_end edit converts to HTML.

Fix:
1. Headers now render as <b>bold</b> in the final HTML (using placeholder
   markers that survive HTML escaping, restored after all other processing)
2. Numbered lists are normalized (extra whitespace after the dot is cleaned)
3. New _strip_md_block() function strips markdown syntax for readable
   plain-text preview during streaming mid-edits

The final _stream_end HTML conversion is unchanged — it still produces
full HTML with parse_mode=HTML. Only the intermediate edits are improved.

Tests:
Added 10 new tests covering:
- Headers converting to bold HTML
- Numbered list preservation and whitespace normalization
- Headers with HTML special characters
- Mixed formatting (headers + bullets + numbers + bold)
- _strip_md_block for inline formatting, headers, bullets, numbers, links
- Streaming mid-edit markdown stripping (initial send + edit)
2026-04-21 21:35:34 +08:00
chengyongruandXubin Ren f900c5bb8e fix(telegram): address code review issues from cherry-pick merge
- Fix critical plain-text fallback that was sending raw HTML tags to
  users: keep raw markdown available for the fallback path
- Extract TELEGRAM_HTML_MAX_LEN (4096) constant to replace hardcoded
  magic number and document the difference from TELEGRAM_MAX_MESSAGE_LEN
- Add fallback to _send_text for extra HTML chunks when HTML parse fails
- Add missing @pytest.mark.asyncio decorator on
  test_send_delta_stream_end_html_expansion_does_not_overflow
2026-04-20 16:58:46 +08:00
2eea82f5ee fix(telegram): split oversized stream buffer mid-flight
Cherry-picked from #3311 (stutiredboy). Streaming edits called
edit_message_text(text=buf.text) without chunking, so once accumulated
deltas crossed Telegram's 4096-char limit an ongoing stream would fail
with BadRequest.

Extracts _flush_stream_overflow helper that edits the first chunk in
place, sends any middle chunks, and re-anchors the buffer to a new
message for the tail so subsequent deltas keep streaming.

Co-Authored-By: stutiredboy <stutiredboy@users.noreply.github.com>
2026-04-20 16:58:46 +08:00
himax12andXubin Ren fd8f08cc83 fix(telegram): convert markdown to HTML before splitting to avoid message length overflow
Cherry-picked from #3316 (himax12). When streaming completes in send_delta(),
the code was splitting raw markdown text by 4000, then converting to HTML.
The markdown-to-HTML conversion adds 10-33% characters, which could push
the result over Telegram's 4096 character limit.

The fix converts markdown to HTML first, then splits by 4096 (actual Telegram
limit), ensuring the edited message always fits.

Fixes #3315
2026-04-20 16:58:46 +08:00
jhkim43andXubin Ren 297b852f6e feat(telegram): change to mid-stream split per review feedback(#2967 PR) 2026-04-20 16:58:46 +08:00
bahtyaandXubin Ren fa98524944 fix(channels): prevent retry amplification and silent message loss across channels
Audited all channel implementations for overly broad exception handling
that causes retry amplification or silent message loss during network
errors. This is the same class of bug as #3050 (Telegram _send_text).

Fixes by channel:

Telegram (send_delta):
- _stream_end path used except Exception for HTML edit fallback
- Network errors (TimedOut, NetworkError) triggered redundant plain
  text edit, doubling connection demand during pool exhaustion
- Changed to except BadRequest, matching the _send_text fix

Discord:
- send() caught all exceptions without re-raising
- ChannelManager._send_with_retry() saw successful return, never retried
- Messages silently dropped on any send failure
- Added raise after error logging

DingTalk:
- _send_batch_message() returned False on all exceptions including
  network errors — no retry, fallback text sent unnecessarily
- _read_media_bytes() and _upload_media() swallowed transport errors,
  causing _send_media_ref() to cascade through doomed fallback attempts
- Added except httpx.TransportError handlers that re-raise immediately

WeChat:
- Media send failure triggered text fallback even for network errors
- During network issues: 3×(media + text) = 6 API calls per message
- Added specific catches: TimeoutException/TransportError re-raise,
  5xx HTTPStatusError re-raises, 4xx falls back to text

QQ:
- _send_media() returned False on all exceptions
- Network errors triggered fallback text instead of retry
- Added except (aiohttp.ClientError, OSError) that re-raises

Tests: 331 passed (283 existing + 48 new across 5 channel test files)

Fixes: #3054
Related: #3050, #3053
2026-04-13 00:30:45 +08:00
bahtyaandXubin Ren 7e91aecd7d fix(telegram): narrow exception catch in _send_text to prevent retry amplification
Previously _send_text() caught all exceptions (except Exception) when
sending HTML-formatted messages, falling back to plain text even for
network errors like TimedOut and NetworkError. This caused connection
demand to double during pool exhaustion scenarios (3 retries × 2
fallback attempts = 6 calls per message instead of 3).

Now only catches BadRequest (HTML parse errors), letting network errors
propagate immediately to the retry layer where they belong.

Fixes: HKUDS/nanobot#3050
2026-04-13 00:30:45 +08:00
stutiredboyandXubin Ren af6c75141f feat(): telegram support stream edit interval 2026-04-08 22:49:33 +08:00
kronk307andXubin Ren e21ba5f667 feat(telegram): add location/geo support
Forward static location pins as [location: lat, lon] content so the
agent can respond to geo messages and pass coordinates to MCP tools.

Closes HKUDS/nanobot#2909
2026-04-08 02:32:19 +08:00
Jiajun XieandXubin Ren 5ee96721f7 ci: add ruff lint check for unused imports and variables
Add CI step to detect unused imports (F401) and unused variables (F841)
with ruff. Clean up existing violations:

- Remove unused Consolidator import in agent/__init__.py
- Remove unused re import in agent/loop.py
- Remove unused Path import in channels/feishu.py
- Remove unused ContentRepositoryConfigError import in channels/matrix.py
- Remove unused field and CommandHandler imports in channels/telegram.py
- Remove unused exception variable in channels/weixin.py
2026-04-07 13:30:49 +08:00
Xubin Ren 09ec9991e1 Merge remote-tracking branch 'origin/main' into pr-2520
Made-with: Cursor

# Conflicts:
#	nanobot/channels/telegram.py
2026-04-06 06:37:54 +00:00
Ilya SemenovandXubin Ren 0d6bc7fc11 fix(telegram): support threads in DMs 2026-04-06 02:44:21 +08:00
FloandXubin Ren fce1e333b9 feat(telegram): render tool hints as expandable blockquotes (#2752) 2026-04-05 16:52:08 +08:00
Xubin Ren 7b852506ff fix(telegram): register Dream menu commands with Telegram-safe aliases
Use dream_log and dream_restore in Telegram's bot command menu so command registration succeeds, while still accepting the original dream-log and dream-restore forms in chat. Keep the internal command routing unchanged and add coverage for the alias normalization path.
2026-04-04 10:31:26 +00:00
Xubin Ren 549e5ea8e2 fix(telegram): shorten polling network errors 2026-04-04 10:26:58 +00:00
Xubin Ren 6e896249c8 feat(memory): harden legacy history migration and Dream UX 2026-04-04 08:41:46 +00:00
FloandXubin Ren 3ada54fa5d fix(telegram): change drop_pending_updates to False on startup (#2686) 2026-04-04 01:52:39 +08:00
FloandXubin Ren 49c40e6b31 feat(telegram): include author context in reply tags (#2605) (#2606)
* feat(telegram): include author context in reply tags (#2605)

* fix(telegram): handle missing attributes in reply_user safely
2026-04-04 01:52:39 +08:00
FloandXubin Ren 2e5308ff28 fix(telegram): remove acknowledgment reaction when response completes (#2564) 2026-04-04 01:52:39 +08:00
FloandXubin Ren 0709fda568 fix(telegram): handle RetryAfter delay internally in channel (#2552) 2026-04-04 01:52:39 +08:00
FloandXubin Ren 0fa82298d3 fix(telegram): support commands with bot username suffix in groups (#2553)
* fix(telegram): support commands with bot username suffix in groups

* fix(command): preserve metadata in builtin command responses
2026-04-04 01:52:39 +08:00
flobo3andXubin Ren 5968b408dc fix(telegram): log network errors as warnings without stacktrace 2026-03-27 22:17:01 +08:00
Michael-lhh e8e85cd1bc fix(telegram): split oversized final streamed replies
Prevent Telegram Message_too_long failures on stream finalization by editing only the first chunk and sending overflow chunks as follow-up messages.

Made-with: Cursor
2026-03-26 22:38:40 +08:00
Xubin Ren 33abe915e7 fix telegram streaming message boundaries 2026-03-26 02:35:12 +00:00