The agent loop signals silent replies (NO_REPLY, cancelled runs, suppressed
errors) by publishing an outbound message with empty content plus the inbound
routing metadata (placeholder_key / local_key). Slack, Telegram and Discord
each implement an empty-content branch in Send() that deletes their streamed
'Thinking...' placeholder — the partial draft left visible in the thread when
delivery is suppressed.
deliverOutbound skipped every text-only empty outbound, so that cleanup
signal never reached channel.Send and the stray partial draft stayed
published (issue #1475). Pass the signal through when placeholder routing
metadata is present; keep skipping bare empty messages so channels without
an empty-content branch never render empty bubbles.
Fixes#1475
* fix: resolve Zalo group name to real chat ID, notify origin on failed forward
Two related root causes behind "forward message to group by name" silently
failing while the agent reports success:
1. No tool let the agent resolve a group's display name (e.g. "Ban Dieu
Hanh") to the chat ID the message tool actually requires. sessions_list
only exposes session keys (numeric group IDs), never human-readable
names, so the agent had no reliable way to turn a name into a real
target and ended up passing the display name itself as `target`.
Adds zalo_list_groups, wrapping the already-used (dashboard picker)
protocol.FetchGroups behind the same optional-interface pattern as
list_group_members/GroupMemberProvider (GroupListProvider on
channels.Manager, gated to zalo_personal via RequiredChannelTypes).
2. When the resulting send fails downstream (e.g. Zalo rejects a bad
chat_id), dispatchOutbound only ever retried/notified media failures on
the same (already-broken) destination, and dropped text-only failures
entirely — even though message.go's own postCrossTargetNotice comment
states forwards must never announce a fake delivery. Because the bus
publish is fire-and-forget, the tool had already returned "sent" and
announced success to the origin chat before the real send was even
attempted.
message.go now tags cross-target forwards with origin channel/chat in
OutboundMessage.Metadata; dispatchOutbound uses it to notify the ORIGIN
chat with the real failure instead of silently dropping it or retrying
against the same invalid target.
* test: cover forward-origin metadata tagging and dispatch failure notice
Extracts dispatchOutbound's error branch into handleSendFailure so it can
be unit tested without driving the consumer loop/goroutine, and adds
coverage for: forward failures notifying the origin chat (not the broken
destination), pre-existing non-forward media/text-only behavior staying
unchanged, message.go tagging cross-target group forwards with origin
metadata alongside group_id, and the new zalo_list_groups tool/Manager
delegator.