Instead of always sending interactive cards, detect the optimal
message format based on content:
- text: short plain text (≤200 chars, no markdown)
- post: medium text with links (≤2000 chars)
- interactive: complex content (code, tables, headings, bold, lists)
SIGHUP and SIGPIPE are not available on Windows. Add hasattr() checks
before registering these signal handlers to prevent AttributeError on
Windows systems.
Fixes compatibility issue introduced in PR #1400.
Previously, mp4 video files were sent with msg_type "file", which meant
users had to download them to play. Feishu requires msg_type "media" for
audio and video files to enable inline playback in the chat.
Changes:
- Add _VIDEO_EXTS constant for video file extensions (.mp4, .mov, .avi)
- Use msg_type "media" for both audio (_AUDIO_EXTS) and video (_VIDEO_EXTS)
- Keep msg_type "file" for documents and other file types
The upload_file API already uses file_type="mp4" for video files via the
existing _FILE_TYPE_MAP, so only the send msg_type needed fixing.
Feishu downloads images with incorrect extensions (e.g. .jpg for PNG files).
mimetypes.guess_type() relies on the file extension, causing a MIME mismatch
that Anthropic rejects with 'image was specified using image/jpeg but appears
to be image/png'.
Fix: read the first bytes of the image data and detect the real MIME type via
magic bytes (PNG: 0x89PNG, JPEG: 0xFFD8FF, GIF: GIF87a/GIF89a, WEBP: RIFF+WEBP).
Fall back to mimetypes.guess_type() only when magic bytes are inconclusive.
Commit 0209ad5 moved `import lark_oapi as lark` inside the start()
method (lazy import) to suppress DeprecationWarnings. This had an
unintended side effect: the import now happens after the main asyncio
loop is already running, so lark_oapi's module-level
loop = asyncio.get_event_loop()
captures the running main loop. When the WebSocket thread then calls
loop.run_until_complete() inside Client.start(), Python raises:
RuntimeError: This event loop is already running
and the _connect/_disconnect coroutines are never awaited.
Fix: in run_ws(), create a fresh event loop with asyncio.new_event_loop(),
set it as the thread's current loop, and patch lark_oapi.ws.client.loop
to point to this dedicated loop before calling Client.start(). The loop
is closed on thread exit.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Extract the _split_message function from discord.py and telegram.py
into a shared utility function in utils/helpers.py.
Changes:
- Add split_message() to nanobot/utils/helpers.py with configurable max_len
- Update Discord channel to use shared utility (2000 char limit)
- Update Telegram channel to use shared utility (4000 char limit)
- Remove duplicate implementations from both channels
Benefits:
- Reduces code duplication
- Centralizes message splitting logic for easier maintenance
- Makes the function reusable for future channels
The function splits content into chunks within max_len, preferring
to break at newlines or spaces rather than mid-word.
GitHub Copilot's API returns tool_calls split across multiple choices:
- choices[0]: content only (tool_calls=null)
- choices[1]: tool_calls only (content=null)
The existing _parse_response only inspected choices[0], so tool_calls
were silently lost, causing the agent to never execute tools when using
github_copilot/ models.
This fix scans all choices and merges tool_calls + content, so
providers that return multi-choice responses work correctly.
Single-choice providers (OpenAI, Anthropic, etc.) are unaffected since
the loop over one choice is equivalent to the original code.
Feishu's GetMessageResource API only accepts 'image' or 'file' as the
type parameter. When downloading voice messages, nanobot was passing
'audio' which caused the API to reject the request with an error.
This fix converts 'audio' to 'file' in _download_file_sync method
before making the API call, allowing voice messages to be downloaded
and transcribed successfully.
Fixes voice message download failure in Feishu channel.
- test_context_prompt_cache: Update test to reflect merged runtime
context and user message (commit ad99d5a merged them into one)
- Remove test_cron_commands.py: cron add CLI command was removed
in commit c05cb2e (unified scheduling via cron tool)