- Attachments now only attached to first chunk when message is split
- Filename sanitized with Path(name).name to prevent path traversal
- Updated streaming tests to match buffer-and-post-at-end pattern
(iOS compatibility)
maintainer edit: cron is also a trigger source, so keep the new CLI-delivered source explicitly named as local trigger across backend, WebUI, docs, and tests.
send_delta popped the buffer before self.send ran, so a transient WeChat
send failure dropped the completed streamed reply: ChannelManager
_send_with_retry re-invokes the same _stream_end message, but the buffer
was already gone, so the retry sent empty content and returned — turning a
delivery retry into silent message loss.
Build `full` from the buffer without popping, send, then clear only after a
successful send. The _stream_end message's own content (set when the manager
coalesces deltas into the end message) is folded into `full` via addition
rather than appended to the buffer, so a retry recomputes the same `full`
from an unchanged buffer instead of double-counting it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WeixinConfig lacked a streaming field, so channels.weixin.streaming was
silently dropped by pydantic and supports_streaming stayed False, forcing the
non-streaming Messages API. Some upstream Anthropic relays drop tool_use
id/name/input on the non-stream path (but handle SSE fine), breaking WeChat
tool calls.
Two parts:
1. Add a streaming field (default True) so WeChat routes LLM calls through the
streaming API. WeChat iLink has no native incremental delivery, so this is
user-invisible — it only changes how the LLM is called.
2. WeChat send_delta previously dropped content, and the manager bypasses send
for the _streamed final answer, so a streamed reply never reached the user.
send_delta now buffers content deltas and flushes the full reply in one shot
at _stream_end (also stopping the typing indicator via send).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mark each processed incoming WhatsApp message as read via neonize's
mark_read(receipt=ReceiptType.READ), so senders see the blue double-check.
The receipt is sent right after the message passes dedup and is best-effort:
any failure is logged at debug level and swallowed, so it never blocks or
breaks message handling.
After a gateway restart or websocket reconnect, the UI stays stuck in
processing state because reconnecting clients only replay running status
when a turn is active, never push idle when no turn is running.
Fix _hydrate_after_subscribe to always push goal_status (running with
started_at when turn is active, idle when no turn is running) so the
frontend can reset its processing indicator on reconnect.
Also fix cmd_stop reporting 'No active task to stop' when a task is
actually processing by draining the pending injection queue in addition
to cancelling active tasks. This prevents mid-turn injection deadlocks
and gives accurate task counts.
ChannelManager coalesces _stream_delta messages by (channel, chat_id)
only. Overlapping streams in the same chat can be merged incorrectly
because deltas from distinct _stream_id values share one buffer.
Include _stream_id in the coalescing key so distinct streams in the
same channel and chat are delivered separately.
richText messages only kept rich_text_list items whose type was
"text", so bold/italic/inlineCode/pre segments were silently dropped;
a message made entirely of formatted segments produced empty content
and fell through to the "unsupported message type: richText" warning.
Now any item carrying text is kept (matching the SDK's own
get_text_list, which keys off the "text" field) and its type is mapped
to Markdown so the formatting survives into the agent's context.
Text and downloadCode within a rich-text item are handled
independently (the SDK treats them as separate via get_text_list /
get_image_list), so an item carrying both a caption and an attachment
no longer drops the file.
The shared httpx.AsyncClient was created without a timeout, so all
requests (including large file/image downloads) used httpx's 5s
default and hit ConnectTimeout/ReadTimeout on uploads. Set an explicit
httpx.Timeout (connect=10s, read/write=30s).
Adds regression tests for formatted-segment preservation, the
all-formatted no-drop case, the text+downloadCode item, and the client
timeout configuration.
Closes#4497
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.
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.
- 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
- 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
WhatsApp can deliver a sender LID instead of a phone number. The channel
already learns the LID->phone mapping at runtime, but only after a message
that carries both values, so the first message from a contact can't be
resolved to a phone number.
Seed the mapping on startup from two sources:
- reverse mapping files the bridge persists in the auth directory
(lid-mapping-<lid>_reverse.json), resolved via get_runtime_subdir so it
respects a custom runtime dir
- a new optional channels.whatsapp.lidMappings config dict for static
mappings (takes precedence over the on-disk files)
Malformed/empty mapping files are ignored rather than failing startup.
Adds tests for both sources, precedence, malformed files and the
no-auth-dir case, plus docs for the new config field.
Maintainer edit: parse Feishu card table columns and rows so forwarded table cards do not fall back to [interactive], and add parser regression coverage.
Feishu cards arriving via WebSocket have a different structure than
expected: elements are nested lists with tag:text/text fields instead
of flat lists with tag:markdown/content. Also extract user_dsl for
richer card data and support body.elements (schema 2.0).
maintainer edit: fix the lint failure, report the active config path, fail fast when the registration response lacks a login URL, and cover the new Feishu login writeback path.
Feishu CardKit content updates can fail without raising, leaving a blank Generating card while the final streamed response skips normal send. Reopen streaming mode and retry once, close blank cards when the first update fails, and fall back to a regular card when final updates still fail.