mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 01:45:58 +00:00
Align IM connections with local channels
This commit is contained in:
@@ -1,107 +1,84 @@
|
||||
# IM Channel Connections
|
||||
|
||||
DeerFlow supports user-owned IM channel connections for Telegram, Slack, and Discord. A logged-in user connects a provider from the frontend, and incoming IM messages run under that DeerFlow user account instead of the raw platform user id.
|
||||
DeerFlow supports user-owned IM channel bindings for Telegram, Slack, and Discord. The feature reuses the existing `channels.*` runtime configuration, so it works in local and private deployments with the same outbound transports already supported by DeerFlow.
|
||||
|
||||
No public IP, OAuth callback URL, or provider webhook is required in this implementation.
|
||||
|
||||
## Configuration
|
||||
|
||||
Enable the top-level `channel_connections` block in `config.yaml`:
|
||||
|
||||
Local/private deployment:
|
||||
Configure the actual IM bots under the existing `channels` block:
|
||||
|
||||
```yaml
|
||||
channel_connections:
|
||||
enabled: true
|
||||
mode: local
|
||||
|
||||
channels:
|
||||
telegram:
|
||||
enabled: true
|
||||
bot_token: $TELEGRAM_BOT_TOKEN
|
||||
bot_username: $TELEGRAM_BOT_USERNAME
|
||||
```
|
||||
|
||||
This mode is intended for a DeerFlow instance running on a developer machine or a private network. Telegram uses the existing long-polling worker, so it does not need a public URL. The frontend `Connect` button returns a Telegram deep link and stores a one-time state locally so the `/start` message can bind the Telegram chat to the current DeerFlow user.
|
||||
|
||||
Public deployment:
|
||||
|
||||
```yaml
|
||||
channel_connections:
|
||||
enabled: true
|
||||
mode: public
|
||||
public_base_url: https://deerflow.example.com
|
||||
encryption_key: $DEER_FLOW_CHANNEL_CONNECTIONS_KEY
|
||||
|
||||
telegram:
|
||||
enabled: true
|
||||
bot_token: $TELEGRAM_BOT_TOKEN
|
||||
bot_username: $TELEGRAM_BOT_USERNAME
|
||||
webhook_secret: $TELEGRAM_WEBHOOK_SECRET
|
||||
|
||||
slack:
|
||||
enabled: true
|
||||
client_id: $SLACK_CLIENT_ID
|
||||
client_secret: $SLACK_CLIENT_SECRET
|
||||
signing_secret: $SLACK_SIGNING_SECRET
|
||||
event_delivery: http
|
||||
bot_token: $SLACK_BOT_TOKEN
|
||||
app_token: $SLACK_APP_TOKEN
|
||||
|
||||
discord:
|
||||
enabled: true
|
||||
client_id: $DISCORD_CLIENT_ID
|
||||
client_secret: $DISCORD_CLIENT_SECRET
|
||||
bot_token: $DISCORD_BOT_TOKEN
|
||||
permissions: "274877975552"
|
||||
```
|
||||
|
||||
`public_base_url` is only required for public callback/webhook deployments. If it is omitted, OAuth redirect URLs are built from the current request origin, which is suitable for localhost development when the provider allows an exact localhost redirect URI. Provider-to-server webhooks such as Slack HTTP Events and Telegram webhooks still need a reachable public URL or a tunnel.
|
||||
Then enable user bindings in `channel_connections`:
|
||||
|
||||
`encryption_key` encrypts provider tokens at rest with Fernet. Telegram deep-link binding does not store user provider tokens, so it can run locally without this key. Slack and Discord connections store OAuth credentials and require a stable key; v1 does not support transparent key rotation, so changing it requires users to reconnect.
|
||||
```yaml
|
||||
channel_connections:
|
||||
enabled: true
|
||||
|
||||
## Frontend Flow
|
||||
telegram:
|
||||
enabled: true
|
||||
bot_username: $TELEGRAM_BOT_USERNAME
|
||||
|
||||
The workspace sidebar shows a Channels group with Telegram, Slack, and Discord. Settings > Channels exposes the management surface for connect, disconnect, and reconnect. Browser state-changing calls use the existing CSRF-aware frontend fetch wrapper.
|
||||
slack:
|
||||
enabled: true
|
||||
|
||||
## Provider Setup
|
||||
discord:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
`channel_connections` does not duplicate provider secrets. It only controls the browser-facing connect UI and stores per-user binding records. Telegram needs `bot_username` only so the frontend can open a deep link.
|
||||
|
||||
## Connect Flow
|
||||
|
||||
Telegram:
|
||||
|
||||
- Register a bot with BotFather.
|
||||
- Configure the bot username and bot token.
|
||||
- Users connect with a deep link: `https://t.me/<bot_username>?start=<state>`.
|
||||
- Local/private delivery uses the existing long-polling channel worker and does not require `public_base_url`.
|
||||
- Production webhook path: `POST /api/channels/webhooks/telegram`, protected by `X-Telegram-Bot-Api-Secret-Token`; webhook delivery requires `webhook_secret` and a public `public_base_url`.
|
||||
- The frontend creates a short one-time code.
|
||||
- The Connect button opens `https://t.me/<bot_username>?start=<code>`.
|
||||
- The existing Telegram long-polling worker receives `/start <code>` and binds that Telegram chat/user to the current DeerFlow user.
|
||||
|
||||
Slack:
|
||||
|
||||
- Create a Slack app with OAuth V2.
|
||||
- Redirect URL: `https://<public_base_url>/api/channels/slack/callback`.
|
||||
- Event request URL: `https://<public_base_url>/api/channels/webhooks/slack/events`.
|
||||
- Required signing secret: Slack's request signing secret, not the deprecated verification token.
|
||||
- Suggested MVP bot scopes: `app_mentions:read`, `chat:write`, `channels:history`, `channels:read`.
|
||||
- Slack events are signature-verified, deduplicated by `event_id`, and then routed to a matching user connection.
|
||||
- In local/private mode, Slack HTTP Events are reported as unavailable unless `public_base_url` is set to a tunnel or public HTTPS URL.
|
||||
- The frontend creates a short one-time code.
|
||||
- The UI shows `Send /connect <code> to the DeerFlow Slack bot.`
|
||||
- The existing Slack Socket Mode worker receives the message and binds the Slack user/team to the current DeerFlow user.
|
||||
|
||||
Discord:
|
||||
|
||||
- Create a Discord application and bot.
|
||||
- Redirect URL: `https://<public_base_url>/api/channels/discord/callback` in public mode, or the matching localhost callback URL in local development if the Discord application is configured to allow it.
|
||||
- DeerFlow starts OAuth with `identify guilds bot applications.commands` and the configured bot permissions.
|
||||
- The Discord Gateway is still handled by `discord.py`; message content may require the privileged Message Content Intent depending on your bot setup.
|
||||
- The frontend creates a short one-time code.
|
||||
- The UI shows `Send /connect <code> to the DeerFlow Discord bot.`
|
||||
- The existing Discord Gateway worker receives the message and binds the Discord user/guild to the current DeerFlow user.
|
||||
|
||||
Codes expire after 10 minutes and are single-use.
|
||||
|
||||
## Runtime Model
|
||||
|
||||
Connection records live in SQL tables under `deerflow.persistence.channel_connections`:
|
||||
|
||||
- `channel_connections`: owner user, provider identity, workspace/guild/team, status, metadata.
|
||||
- `channel_credentials`: encrypted access/refresh/bot tokens.
|
||||
- `channel_oauth_states`: one-time OAuth/deep-link states.
|
||||
- `channel_oauth_states`: one-time connect codes and Telegram deep-link state.
|
||||
- `channel_conversations`: connection-scoped IM conversation to DeerFlow thread mapping.
|
||||
- `channel_webhook_deliveries`: provider webhook dedupe records.
|
||||
- `channel_credentials`: reserved for future provider-token flows, not used by the local/private binding flow.
|
||||
|
||||
Incoming messages that resolve to a connection carry `connection_id`, `owner_user_id`, and `workspace_id`. `ChannelManager` uses `owner_user_id` as the DeerFlow run user id and preserves the platform user id as `channel_user_id`. Legacy operator-owned channels keep the existing JSON `ChannelStore` behavior when no `connection_id` is present.
|
||||
Incoming messages that resolve to a connection carry `connection_id`, `owner_user_id`, and `workspace_id`. `ChannelManager` uses `owner_user_id` as the DeerFlow run user id and preserves the raw platform user id as `channel_user_id`.
|
||||
|
||||
## Security Notes
|
||||
|
||||
- OAuth state tokens are one-time and short-lived.
|
||||
- Provider tokens are never returned from browser APIs.
|
||||
- Public callback/webhook routes bypass cookie auth only because they validate provider state/signatures/secrets themselves.
|
||||
- Slack and Telegram webhooks skip CSRF because they are called by providers, not browsers.
|
||||
- Logs should never include access tokens, refresh tokens, bot tokens, OAuth codes, or raw signed webhook bodies.
|
||||
- Browser APIs remain authenticated and CSRF-protected.
|
||||
- Connect codes are random, short-lived, and single-use.
|
||||
- Provider bot tokens remain in `channels.*` and are never returned to the browser.
|
||||
- This implementation does not add public provider callback or webhook routes.
|
||||
|
||||
Reference in New Issue
Block a user