deer-flow/backend/app/channels/capabilities.py
Totoro c994826d06
feat(channels): generalize group notifications and add opt-in task/goal IM notices (#6903)
* feat(channels): generalize IM group targets and add task/goal run notifications

- Generalize proactive group notification capability (proactive_group_notifications), binding validation, and scheduled task group selection across Feishu, QQ, and WeCom while keeping WeChat personal-only.
- Add QQ group active notification delivery via POST /v2/groups/{group_openid}/messages (msg_type=2) and WeCom group active notification delivery via WS Client.sendMessage(chatid, body) with bot_id:group:chatid workspace isolation.
- Generalize notification outbox sources via migration 0043_runtime_im_notices (source_kind, source_key, run_id, nullable task_id/task_run_id) with backward-compatible SQLite/PostgreSQL upgrades.
- Add opt-in ordinary task & goal run IM notifications (run_completed, run_failed, human_input_requested) on the shared outbox, delivery worker, renderer, and settings UI.

* fix(notifications): address review feedback and group/runtime notification edge cases

* test(migrations): update CURRENT_HEAD to 0043_runtime_im_notices across migration chain tests

* fix(channels): preserve group binding names and legacy authorization

Carry owner-chosen group names through existing connect state, keep private bindings out of legacy group fallback, and restore completion metadata retries. Cover real binding and worker finalization paths and synchronize notification docs.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-15 10:30:55 +08:00

44 lines
2.7 KiB
Python

"""Static per-provider channel capabilities.
Dependency-free on purpose: the scheduler and the Settings API read it without
importing any channel SDK. ``tests/test_channel_capabilities.py`` keeps it in
sync with the channel registry and the channel classes.
- ``supports_streaming``: the fallback the channel manager uses when no live
channel instance is running (a running channel answers for itself).
- ``proactive_notifications``: the provider's channel class implements
``Channel.send_notification`` (push with no inbound message to reply to).
Only these providers get scheduled-task notification rows; to add one, see
the ``Channel.send_notification`` docstring.
- ``proactive_group_notifications``: the provider's channel class implements
``Channel.send_group_notification_for_owner`` for explicit group notification
bindings.
"""
from __future__ import annotations
CHANNEL_CAPABILITIES: dict[str, dict[str, bool]] = {
"buzz": {"supports_streaming": True, "proactive_notifications": False, "proactive_group_notifications": False},
"dingtalk": {"supports_streaming": False, "proactive_notifications": False, "proactive_group_notifications": False},
"discord": {"supports_streaming": False, "proactive_notifications": False, "proactive_group_notifications": False},
"feishu": {"supports_streaming": True, "proactive_notifications": True, "proactive_group_notifications": True},
"github": {"supports_streaming": False, "proactive_notifications": False, "proactive_group_notifications": False},
"qq": {"supports_streaming": False, "proactive_notifications": True, "proactive_group_notifications": True},
"slack": {"supports_streaming": False, "proactive_notifications": False, "proactive_group_notifications": False},
"telegram": {"supports_streaming": True, "proactive_notifications": False, "proactive_group_notifications": False},
"wechat": {"supports_streaming": False, "proactive_notifications": True, "proactive_group_notifications": False},
"wecom": {"supports_streaming": True, "proactive_notifications": True, "proactive_group_notifications": True},
}
def supports_proactive_notifications(provider: str) -> bool:
"""Whether scheduled-task notices can be pushed to ``provider`` (unknown provider: False)."""
entry = CHANNEL_CAPABILITIES.get(provider) if isinstance(provider, str) else None
return bool(entry) and entry.get("proactive_notifications") is True
def supports_proactive_group_notifications(provider: str) -> bool:
"""Whether explicit group notices can be pushed to ``provider`` (unknown provider: False)."""
entry = CHANNEL_CAPABILITIES.get(provider) if isinstance(provider, str) else None
return bool(entry) and entry.get("proactive_group_notifications") is True