When LLM generates faster than channel can process, asyncio.Queue
accumulates multiple _stream_delta messages. Each delta triggers a
separate API call (~700ms each), causing visible delay after LLM
finishes.
Solution: In _dispatch_outbound, drain all queued deltas for the same
(channel, chat_id) before sending, combining them into a single API
call. Non-matching messages are preserved in a pending buffer for
subsequent processing.
This reduces N API calls to 1 when queue has N accumulated deltas.
The bridge's pn/sender fields don't consistently map to phone/LID
across different versions. Classify by JID suffix instead:
@s.whatsapp.net → phone number
@lid.whatsapp.net → LID (internal WhatsApp identifier)
This ensures allowFrom works reliably with phone numbers regardless
of which field the bridge populates.
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
Make channel delivery failures raise consistently so retry policy lives in ChannelManager rather than being split across individual channels. Tighten Telegram stream finalization, clarify sendMaxRetries semantics, and align the docs with the behavior the system actually guarantees.
Read the default timezone from the agent context when wiring the cron tool so startup no longer depends on an out-of-scope local variable. Add a regression test to ensure AgentLoop passes the configured timezone through to cron.
Made-with: Cursor
Make cron list output render one-shot and run-state timestamps in the same timezone context used to interpret schedules. This keeps scheduling logic and user-facing time displays consistent.
Made-with: Cursor
Make cron use the configured agent timezone when a cron expression omits tz or a one-shot ISO time has no offset. This keeps runtime context, heartbeat, and scheduling aligned around the same notion of time.
Made-with: Cursor
Add agent-level timezone configuration with a UTC default, propagate it into runtime context and heartbeat prompts, and document valid IANA timezone usage in the README.
Replace the flatten/unflatten approach (merging extra_content.google.*
into provider_specific_fields then reconstructing) with direct pass-through:
parse extra_content as-is, store on ToolCallRequest.extra_content, serialize
back untouched. This is lossless, requires no hardcoded field names, and
covers all three parsing branches (str, dict, SDK object) plus streaming.
Handle string and dict-shaped responses from OpenAI-compatible backends so non-standard providers no longer crash on missing choices fields. Add regression tests to keep SDK, dict, and plain-text parsing paths aligned.
- Prevent repeated retries on expired sessions in the polling thread
- Stop sending messages to invalid agent sessions to eliminate noise logs and unnecessary requests
Keep the mypy-friendly optional execute signatures while returning clearer errors for missing arguments and locking that behavior with regression tests.
Made-with: Cursor
Keep the channel enhancements aligned with the current codebase while preserving a simpler product surface. This keeps QQ, Feishu, Telegram, and WhatsApp improvements together, removes the extra Telegram-only tool hint toggle, and makes WhatsApp mention-only groups actually work.
Keep cron state workspace-scoped while only migrating legacy jobs into the default workspace. This preserves seamless upgrades for existing installs without polluting intentionally new workspaces.
Move channel-specific login logic from CLI into each channel class via a
new `login(force=False)` method on BaseChannel. The `channels login <name>`
command now dynamically loads the channel and calls its login() method.
- WeixinChannel.login(): calls existing _qr_login(), with force to clear saved token
- WhatsAppChannel.login(): sets up bridge and spawns npm process for QR login
- CLI no longer contains duplicate login logic per channel
- Update CHANNEL_PLUGIN_GUIDE to document the login() hook
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>