DeerFlow IM Channel Connections 实战指南:用户自有渠道的连接绑定、消息分发与文件管线
2026/9/7 18:03:27 网站建设 项目流程

DeerFlow IM Channel Connections 实战指南:用户自有渠道的连接绑定、消息分发与文件管线

【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow

本文是 DeerFlow 中IM Channel Connections(用户自有 IM 渠道连接)体系的技术实战指南。该特性允许在 Telegram、Slack、Discord、飞书/Lark、钉钉、微信、企业微信 WeCom 与 Buzz 上,把某个平台账号/工作区绑定到某一个 DeerFlow 用户,从而让经渠道发起的每一次运行都落进该用户的专属"桶"(记忆、上传文件、产物与自定义 Agent)。读完本文,你将掌握:Connect 一次性绑定码的完整时序、数据库层面实现的"单一活跃归属者"不变量、绑定后消息从入站到出站的完整分发链路、同步/流式两类渠道的差异化运行策略,以及config.yaml中每个开关(含 Buzz 的订阅模型与信任模型)的真实含义与运维后果。

原始设计文档见 IM_CHANNEL_CONNECTIONS.md,高层索引(配置旋钮、消息流、组件清单)见 AGENTS.md 中 "IM Channels System" 一节,以及渠道子系统自己的 AGENTS.md。


一、特性概览:绑定层在 bot 凭证之上补上了什么

DeerFlow 的 IM 渠道连接在语义上是一个**"按 DeerFlow 用户"的绑定层**,它叠在既有的channels.*出站/入站 bot 凭证之上,而不是重新发明一套出站传输。因此本地部署与私有化部署可以复用 DeerFlow 已经支持的全部出站通道,且整套实现不需要公网 IP、OAuth 回调 URL,也不需要向渠道平台注册 webhook——所有传输都建立在各平台已有的长连接/轮询/Socket Mode 机制之上。

为什么有了 bot 凭证还不够?绑定层在既有凭证能力之外,新增了三样东西:

  1. 归属者身份(Owner identity)—— 每一个(provider, external account, workspace)三元组都唯一映射到一个 DeerFlow 账号(owner_user_id)。由此连接发起的每个 run,都运行在该归属者的桶里(记忆、上传、产物、自定义 Agent 均按此隔离)。
  2. 一次性绑定码(One-time bind codes)—— 浏览器 Connect 流程铸造一个短期随机码(secrets.token_urlsafe(16),600 秒 TTL、单次使用),且只在发起用户的浏览器中展示。各平台 worker 在应用allowed_users过滤之前消费/connect <code>(Telegram 使用 deep link 方式的/start <code>),因此尚未进入白名单的用户也能完成首次绑定。
  3. 严格的归属转移(Strict ownership transfer)—— 最近一次成功的绑定"后到者胜";upsert_connection会撤销其他归属者针对同一外部身份的有效行。数据库层的部分唯一索引uq_channel_connection_active_identityWHERE status != 'revoked')让该不变量在并发写入下也是无竞态(race-free)的。

一句话概括安全边界:Connect 码是"绑定时刻"的防线,不是"聊天时刻"的防线。绑定完成后,普通消息仍由既有的allowed_users按原样把关。

二、Connect 绑定码流程:浏览器发起、worker 消费、manager 不碰码

关键设计约束是:浏览器发起;provider worker 消费码;调度中心(ChannelManager)永远看不到码本身。

在存储实现上(channel_connections/sql.py),consume_oauth_state使用一次条件 UPDATE:只有能把consumed_at从 NULL 翻转为当前时刻的那个 writer 才算赢,两个 worker 并发消费同一码时只有一个能成功——这就是"单次使用"的无竞态落地点。码在数据库中不以明文落盘,而是先经 SHA-256 哈希(hash_state)再以state_hash作为主键存储(见 model.py 中ChannelOAuthStateRow)。

三、单一活跃归属者:数据库索引才是"真话"

整个归属转移体系的核心论点是:应用代码永远不需要显式地"撤销上一任归属者"。原因是,当一次重新使用同一身份的 upsert 触发唯一约束冲突而失败后,失败方会"输掉"这场竞争,并在下一次重试时对着现在已可见的 revoked 状态行动。

尘埃落定之后的状态是:新连接connection_id=Bowner=Bstatus=connected;旧连接connection_id=Aowner=Astatus=revoked

源码层面可以看到两层配合(channel_connections/model.py):

  • 普通唯一约束uq_channel_connection_owner_provider_identityowner_user_id + provider + external_account_id + workspace_id)保证同一归属者不会为同一外部身份重复建行;
  • 部分唯一索引uq_channel_connection_active_identityprovider + external_account_id + workspace_idWHERE status != 'revoked')保证任何时刻、全库范围内一个外部身份至多只有一条未撤销行,SQLite 与 PostgreSQL 均支持这种 partial unique index。

而 sql.py 中的upsert_connection把事务拆成了"先撤销其他归属者的 active 行(连带删除其channel_credentials),再提交自己的 connected 行",冲突时回滚并最多重试 3 次(_UPSERT_MAX_ATTEMPTS = 3),每次重试都重读当前可见状态,因此并发归属转移在真实竞争下也能收敛。

同一个不变量还保护着find_connection_by_external_identity查找——它是ChannelManager._get_bound_identity_rejection判断"这条消息是否来自已绑定身份"的底层依据。因为未撤销的行在任何时刻只会解析到唯一归属者,所以这条查找天然不会出现一身份映射多归属者的歧义。

四、绑定之后:Provider 消息如何走完全程

一旦连接绑定成功,每条入站消息都会经由ChannelManager走同一条路径。Slack/Discord(无流式)与飞书/Telegram(有流式)只在run 边界处分叉:

4.1 入站容量与过载行为

三个顶层channels设置控制着 MessageBus/manager 的生命周期(默认值、语义与启动校验分别实现在 service.py、message_bus.py 与 manager.py 中):

配置项默认值含义
channels.inbound_queue_maxsize1000排队消息 + provider 侧仍在做最终身份/ack 准备的预留之和
channels.max_concurrency5常驻ChannelManagerworker 的精确数量
channels.shutdown_grace_period_seconds3主动 handler 被取消前允许的排空(graceful draining)时间上限

active handler 在这些 worker内联执行(inline),所以突发流量不会造成"每条消息一个 task"。由此可得 manager 实际持有的最大在途 intake = 待处理容量 + 固定 worker 数,是有上界的。

一个值得注意的实现细节是:准入(admission)永不等待队列空间。因为让生产者协程"等待队列腾位"只会把无界积压搬到队列外面,并不会缓解过载。因此在容量打满时:

  • Slack、Discord、飞书/Lark、钉钉、Telegram、微信、WeCom:在 DeerFlow 发出工作确认(working acknowledgment)之前就丢弃新消息,MessageBus以限速方式输出一条带累计拒绝计数的告警日志;
  • Buzz:保持该渠道的按渠道回放水位(per-channel replay watermark)不变并重连,让 relay 的历史回放机制把该事件重新投递进来;
  • GitHub webhook 扇出:返回503,GitHub 侧把投递记为失败,运维人员或恢复任务可通过 GitHub 的 Recent Deliveries 界面或 REST redelivery API 手动重试(GitHub 不会自动重试失败的投递)。

4.2 优雅停机:先关准入、后断传输

关停顺序决定了会不会出现"handler 用已关闭的传输发消息"。实现上:先关闭准入(admission)并取消后续 watcher,但保持 provider 传输存活,让 worker 在shutdown_grace_period_seconds内把已接受消息排空;宽限期耗尽后,取消 active handler、丢弃从未开始的队列条目,并 await 每一个 manager 持有的 worker 与 watcher。从 SDK 线程提交的 provider 协程同样被保留、取消并 await,之后该渠道才会拆除 SDK 资源——因此一次成功的停机会保证没有任何 owned handler 还能使用已关闭的传输。Gateway 外层停机超时仍是进程级的总预算:如果它取消了清理动作,服务会保留其传输与单例,而不是虚报"成功停机"或掩盖未完成的归属清理。

五、同步 vs 流式渠道:一切分歧源于一个能力位

两种路径在ChannelRunPolicy.supports_streaming处分叉。该能力位来自ChannelManager里的渠道能力登记表CHANNEL_CAPABILITIES(manager.py,也见渠道实现各自声明的supports_streaming(),例如 telegram.py、feishu.py、wecom.py、buzz.py):

  • 同步渠道(supports_streaming=false):Slack、Discord、DingTalk—— manager 调用runs.wait()阻塞等待,从最终 state 中抽取 AI 文本,然后一次性publish_outbound(is_final=True)
  • 流式渠道(supports_streaming=true):Feishu/Lark、Telegram、WeCom—— manager 调用runs.stream(messages-tuple + values),逐 chunk 以节流方式publish_outbound(is_final=False),各平台用各自的"进行中"载体呈现中间结果(Telegram 编辑占位消息、飞书 patch 运行中的卡片、WeCom 走PUT /v1.0/card/streaming),最终再发布is_final=True

GitHub 是一个特殊分支:它的渠道策略是fire_and_forget=True(见 run_policy.py 中的ChannelRunPolicy描述符,以及 github/run_policy.py 的登记),manager 只需runs.create()并在 run 进入pending后即返回,不做任何出站回复——因为 GitHub agent 是在自己的沙箱里通过ghCLI 直接回帖的。完整 GitHub 流程见 GITHUB_AGENTS.md。

5.1 ChannelRunPolicy:一份策略数据类承载全部渠道差异

从源码看,CHANNEL_RUN_POLICY是一个"渠道名 → 策略"注册表,新增一个 webhook/特殊渠道只需注册一行策略而不是改动 manager 的多处方法(run_policy.py)。值得了解的字段包括:is_interactive(False 时关闭澄清提问,适配"无人同步在场"的自主长任务)、default_recursion_limit(把recursion_limit抬到max(现有值, 配置值),GitHub 这类长跑任务需要更高上限,普通聊天回合则保持全局默认)、credentials_provider(向run_context注入平台专用凭证的异步钩子,异常会被捕获降级为只读运行)、requires_bound_identity(False 时跳过按发送者的绑定身份闸门——webhook 渠道用 HMAC 做真实性认证,没有/connect握手)、fire_and_forgetserialize_thread_runs等。

六、归属者作用域的文件存储(Owner-scoped File Storage)

ChannelManager_handle_chat顶部只解析一次存储归属者:_channel_storage_user_id(msg)会做安全化处理(sanitized owner id),并在无绑定身份的渠道上回退到safe(msg.user_id)(与_resolve_run_params的 run 身份保持一致);只有当任何身份都不可用时才返回None。这个值被贯穿到整个文件管线:

  • 同时用作run_context["user_id"](run 身份);
  • 同时用作 memory、uploads、outputs 的存储桶。

于是 agent 读写的桶,永远是渠道文件被暂存的那个桶——二者不会错位

缓存下来的归属者值在阻塞路径(runs.wait)与流式路径(_handle_streaming_chat)之间是复用的——即使未来某个Channel.receive_file返回了被改写过的InboundMessage,uploads 与产物投递依然命中同一个桶。

七、IM 附件管线:下载、落桶、注入与发现

入站文件(图片、文档)先经各渠道自己的Channel.receive_fileprovider 特定物化。物化后存在两条路:

  • 走共享元数据路径的附件_ingest_inbound_files暂存,元数据写入HumanMessage.additional_kwargs.files,然后UploadsMiddleware为当前消息注入一段<current_uploads>上下文块;
  • 部分 provider 在下载时直接消费自己的描述符,把占位符或消息文本改写为最终的虚拟路径(或一条失败提示)。

历史上传不会在后续轮次被自动注入;agent 需要借助list_uploaded_files工具主动发现历史文件。飞书/Lark 的入站资源流在被持久化或同步进非本地沙箱之前有一个20,000,000 字节上限;超限资源与个别文件的路径失败会以失败占位符形式呈现在消息文本中,而不会中断同一条入站消息里后续附件的处理

八、配置详解:两步启用 + 一个必须知晓的升级语义

8.1 第一步:在既有的channels块中配置各平台 bot

实际的 IM bot 凭证仍然位于既有的channels块(环境变量$VAR形式由AppConfig.resolve_env_variables在加载时递归展开,见 app_config.py;完整示例可对照 config.example.yaml):

channels: inbound_queue_maxsize: 1000 max_concurrency: 5 shutdown_grace_period_seconds: 3 telegram: enabled: true bot_token: $TELEGRAM_BOT_TOKEN slack: enabled: true bot_token: $SLACK_BOT_TOKEN app_token: $SLACK_APP_TOKEN discord: enabled: true bot_token: $DISCORD_BOT_TOKEN feishu: enabled: true app_id: $FEISHU_APP_ID app_secret: $FEISHU_APP_SECRET dingtalk: enabled: true client_id: $DINGTALK_CLIENT_ID client_secret: $DINGTALK_CLIENT_SECRET wechat: enabled: true bot_token: $WECHAT_BOT_TOKEN wecom: enabled: true bot_id: $WECOM_BOT_ID bot_secret: $WECOM_BOT_SECRET buzz: enabled: true relay_url: wss://buzz.example.com private_key: $BUZZ_PRIVATE_KEY # hex or nsec1…

8.2 第二步:在channel_connections中开启用户绑定

channel_connections: enabled: true # Auth-enabled deployments require ordinary IM messages to come from a # connected DeerFlow user by default. Set this to false only for legacy # operator-owned/open-bot deployments that intentionally route unbound # platform users to platform-ID user buckets. require_bound_identity: true telegram: enabled: true bot_username: $TELEGRAM_BOT_USERNAME slack: enabled: true discord: enabled: true feishu: enabled: true dingtalk: enabled: true wechat: enabled: true wecom: enabled: true buzz: enabled: true

几个关键语义:

  • channel_connections不复制 provider 密钥。它只控制浏览器侧的 Connect UI,并存储按用户的绑定记录。Telegram 需要bot_username的唯一理由,是让前端能拼出 deep link。
  • 配置模型见 channel_connections_config.py:顶层的enabledrequire_bound_identity之下,每个渠道是独立的子配置;其中telegram.configured取决于bot_username是否非空,其余渠道只要enabled即为 configured。
  • 强制绑定身份语义:当channel_connections.enabledrequire_bound_identity同时为 true,启用认证的部署会在创建 DeerFlow thread/run之前拒绝普通的未绑定 IM 消息,用户必须先在 DeerFlow Settings 里完成渠道连接。而关闭认证的本地模式仍会把渠道消息路由给默认用户;需要恢复旧式 open-bot 行为时,显式设置require_bound_identity: false即可。

8.3 升级注意事项(重要)

require_bound_identity的默认值是true。这意味着:升级前已开启channel_connections.enabled: true的认证部署,在本字段引入后,会开始拒绝普通的未绑定 IM 消息。对于刻意允许未绑定平台用户创建 DeerFlow run 的旧式 operator-owned/open-bot 部署,请在升级之前显式设置require_bound_identity: false并重启服务。

8.4 各平台的 Connect 操作流程

  • Telegram:前端生成一次性短码 → Connect 按钮打开https://t.me/<bot_username>?start=<code>→ 既有 Telegram 长轮询 worker 收到/start <code>,把该 Telegram chat/user 绑定到当前 DeerFlow 用户。
  • Slack:前端生成一次性短码 → UI 提示Send /connect <code> to the DeerFlow Slack bot.→ 既有 Slack Socket Mode worker 收到消息后绑定该 Slack user/team。
  • Discord:前端生成一次性短码 → UI 提示Send /connect <code> to the DeerFlow Discord bot.→ 既有 Discord Gateway worker 收到消息后绑定该 Discord user/guild。
  • 飞书/Lark、钉钉、微信、WeCom:前端生成一次性短码 → UI 提示Send /connect <code> to the DeerFlow <Provider> bot.→ 已运行的 long-connection 或轮询 worker 收到消息后绑定平台 user/workspace 身份。
  • Buzz:见下一节单独展开。

对带allowed_users白名单的 provider(Telegram、Slack、钉钉、微信等),有效的/connect <code>(或 Telegram 的/start <code>会在白名单检查之前被消费。这是有意为之:尚未进入白名单、bot 因此从未见过其平台身份的用户,仍然可以完成第一次由浏览器发起的绑定。绑定完成后,allowed_users继续如常把关普通(非绑定)消息。

九、Buzz:无开发者控制台的特殊成员身份

Buzz 与上述 bot/app 凭证渠道不同——它没有独立的开发者控制台:DeerFlow 以普通成员身份加入 relay。需要为这个身份生成一对 Nostr 密钥,以下所有描述都指其hex 公钥

9.1 两步式 onboarding(缺一不可)

Relay 成员身份与渠道成员身份是两回事,只做第一步会产生一个"能连上、能通过认证、但收不到任何东西"的 connector:

  1. 把 pubkey 注册为 relay 成员buzz-admin add-member --pubkey <hex>。这一步让该身份能够认证(NIP-42)并能发布消息。
  2. 把它加入应参与的每个渠道buzz channels add-member --channel <uuid> --pubkey <hex> --role bot。聊天事件只会投递给渠道成员,且 relay 会拒绝任何p-mentions 非成员的消息(报错mentioned pubkeys are not channel members)——缺这一步,connector 既听不到 mention 也答不了 mention。

配置上只认两个键:channels.buzz.relay_urlchannels.buzz.private_key(hex 或nsec1…),再开启channel_connections.buzz。Connect 时前端生成一次性短码,UI 提示Send /connect <code> to the DeerFlow Buzz bot.,Buzz relay-loop worker 收到以 DM 或双方共同所在渠道 @mention 发送的消息后,把发送者的 Nostr pubkey 绑定到当前 DeerFlow 用户。

两个重要事实:

  • 渠道是自动发现的,不需要写进config.yaml每次连接时 connector 会向 relay 查询该身份属于哪些渠道并逐一下订;之后新加入的渠道实时生效(relay 下发 membership 通知即开始监听,移除即停止),无需重启或重连。日志里出现channel discovery returned no channels说明第 2 步没做。
  • 依赖buzzextra(uv sync --extra buzz,为coincurve库)。detect_uv_extras.py(以及通过 backend/Dockerfile 的 Docker/生产构建)会在config.yamlchannels.buzz.enabled: true时自动探测并保留该 extra——这和browserextra 为browser_navigate被自动探测的方式一致。

9.2 Buzz 订阅模型

Buzz relay 只向渠道作用域的订阅投递聊天事件,因此 connector 的订阅形态是刚性的:全局REQ {"kinds":[9]}会被接受并应答EOSE,但不会扇出任何聊天事件;单条订阅也无法覆盖多个渠道(多值#h匹配不到任何东西)。所以在每次连接、NIP-42 认证完成后:

订阅过滤器用途
buzz-discovery{"kinds":[39000]}历史查询:精确列出该身份所属的全部渠道(每个渠道一条已存事件,随后EOSE)。提供各渠道的名称与类型——DM 的 mention 豁免也读取这里。不要#p收窄它——那会什么都匹配不到。
buzz-membership{"kinds":[44100,44101], "#p":["<our pubkey>"], "since": …}实时成员资格通知。44100(added)立刻订阅新渠道;44101(removed)关闭对应渠道订阅。这就是"新增渠道无需重启"的原因。
buzz-chat-<uuid>{"kinds":[9], "#h":["<uuid>"], "since": …}每个已发现渠道一条——唯一真正能收到消息的形态

值得运维人员记住的五个推论:

  • 回放按渠道分别跟踪。每个渠道有自己的since水位,只有 DeerFlow 实际处理过的事件才会推进它。若共用一条水位,繁忙渠道会把游标拖过安静渠道的未读消息,导致重连后跳过它们;而按渠道游标的最坏代价只是重复投递(manager 的入站去重会吸收),永远不会漏消息。
  • 成员资格事件只存在于实时流。relay会存储44100/44101,若不设since,每次连接都会把整段成员资格历史当作"刚刚发生"重放:对每条已存储的 add 重跑一次渠道发现(一次连接会看到多条channel discovery complete,渠道还会被记为<unnamed>),重订已被移除的渠道,并短暂退订仍在的渠道。因此订阅锚定在 socket 打开的瞬间并往前留 60 秒余量,让连接/认证握手期间发生的成员变化或 relay 时钟偏差仍能被捕捉。
  • 渠道订阅数有上限(256)。渠道列表来自网络,与其他远端输入一样有界。触顶时新渠道会被拒绝,并在per-channel subscription limit reached告警中点名,而不会驱逐任何在工作的订阅。
  • relay 关闭的订阅会被重新打开,每次连接最多重试 3 次。relay 丢弃订阅时 socket 上是静默失败的:聊天订阅哑掉一个渠道、buzz-membership让 DeerFlow 再也学不到入退群、buzz-discovery杀死完整性清扫。所以CLOSED帧不只是被记录而是被恢复,静默的订阅总会以 WARNING 级被点名。当 relay 明示原因说明"该订阅已不属于你"时(NIP-01/NIP-42 的auth-required:/restricted:/blocked:/invalid:前缀,或 buzz-relay 自己的吊销措辞),恢复会被跳过——因为重发同一条 REQ 只会和 relay 对抗;其余任何原因(包括不带任何原因的CLOSED)都被当作小故障重试,以 3 次尝试为兜底,之后保持静默直到下次重连从零重建。
  • 已知边界:单渠道在断连期间积压超过 2000 条未读,会丢掉最旧的一批。relay 把单订阅历史投递上限设为 2000 条且最新优先(即使带since也如此)。DeerFlow 只处理收到的部分,渠道水位随之越过其余消息——那些更旧的消息永远不被投递也不被重试。设计中其余所有缺口都偏向"重复投递"(由入站去重吸收),这是唯一会"跳过"的遗留情形,且需要"断连 + 单渠道 >2000 条积压"两个条件同时成立。

9.3 Buzz 信任模型

在团队自营的 relay 上,relay 运营者未必等于 DeerFlow 运营者,所以必须分清什么被密码学验证、什么只是被信任:

已验证(每次入站事件都在密码学上成立):DeerFlow 从收到的载荷重算每个事件的 NIP-01 id,并用 BIP-340 Schnorr 签名对照声明中的pubkey做验证,验证通过前事件不能影响任何东西。因此 relay 无法改写成员消息、无法把一位作者的签名搬到另一载荷上、也无法冒称一个它不掌握密钥的已白名单作者。这一点同样适用于/connect绑定——relay 无法把别人的 pubkey 绑到攻击者的 DeerFlow 账号。验证失败的事件会被丢弃并告警。

被信任(未验证):kind-39000 渠道元数据的作者身份。Buzz 用 relay 自己的密钥对发布渠道发现事件,但现有配置没有标识这把密钥(relay_url是网络地址而非签名密钥),DeerFlow 只能证明该事件被某个成员签名。由于渠道发现与订阅恰好由这些事件驱动,一条伪造的 kind-39000 会有两个(而非一个)后果:

  1. 它可以把渠道标记为type: "dm",从而放宽该渠道的require_mention要求;
  2. 它可以让 DeerFlow为一个伪造者指定的渠道打开聊天订阅——因为 DeerFlow 监听的渠道集合就是它持有元数据的渠道集合。

两者都无法让任何东西被执行allowed_users白名单与逐事件签名验证是两条独立的闸门:无论渠道类型如何、订阅如何被打开,非白名单作者一律被丢弃。(2) 的爆炸半径是"relay 把自己流量读回给一个忽略它的订阅者",且受 256 订阅上限约束(只拒绝新订阅、不驱逐在工作的订阅,诱导出来的订阅顶不掉真实渠道)。伪造的 kind-44100 成员通知同理,只是其p标签会在本地被重新核对,所以它至少必须点名本身份。如果你需要在一个不能全信成员的 relay 上让 mention 要求不可伪造,就把这些渠道排除在mention_free_channels之外,并把 DM 检测当作便利功能而非安全边界。

默认拒绝的白名单:与其他 provider(空allowed_users= 允许所有人)不同,channels.buzz.allowed_users刻意是默认拒绝——空列表意味着没人能触发 run,DeerFlow 会就此打一条启动告警。应把每个允许触达 agent 的成员 pubkey(hex 或npub1…)加进去。个别丢弃只在 DEBUG 级记录。

绑定身份:pubkey 一旦完成/connect,其入站消息就解析到该连接并在绑定 DeerFlow 用户身份下运行(记忆、文件、产物都落在该用户桶里)。绑定按 relay 主机隔离——同一个 pubkey 在不同 relay 上是不同身份,必须分别绑定。

绑定码使用 128 位随机性,10 分钟后过期,且单次使用。

十、运行时模型:四张 SQL 表与身份字段

连接记录落在deerflow.persistence.channel_connections下的 SQL 表中(模型定义见 model.py,SQL 仓库见 sql.py):

职责
channel_connections归属用户、provider 身份、workspace/guild/team、状态、元数据
channel_oauth_states一次性 Connect 码与 Telegram deep-link 状态
channel_conversations连接作用域的 IM 会话 → DeerFlow thread 映射
channel_credentials为未来的 provider-token 流程预留;本地/私有绑定流程不使用

解析到某条连接的入站消息携带connection_idowner_user_idworkspace_id三个字段。ChannelManagerowner_user_id用作 DeerFlow run 用户 id,同时保留原始平台用户 id 为channel_user_id

运行时 provider 凭证是部署级 bot 秘密,不是用户所有的连接凭证。它们既可以来自config.yamlchannels.*,也可以来自浏览器运行时配置流程——后者通过ChannelRuntimeConfigStore持久化,让本地/私有部署无需编辑 YAML 即可配置 bot。运行时存储的本地回退是明文 JSON 文件,owner-only 文件权限(0600;仅当 DeerFlow 数据目录已被视为秘密存储时才使用它。微信扫码登录的认证状态遵循同一本地运行时模型,并可能把二维码导出的 bot token 持久化在渠道状态目录里。

十一、安全笔记汇总

  • 浏览器 API 保持已认证与 CSRF 防护;
  • Connect 码 128 位随机、短时效、单次使用;
  • 运行时 provider bot token 是共享部署秘密。运行时设置响应会脱敏密码字段,变更运行时/渠道 worker 的 API 需要 admin 用户;
  • 存储的按连接凭证使用channel_credentials加密路径。若存量的凭证材料无法解密,DeerFlow 将其视为不可用,而不是使用损坏的秘密(ChannelCredentialCipher基于 Fernet,见 sql.py);
  • 本地明文运行时凭证回退(0600)已在上一节说明;在部署专用 secret backend 之前,非本地部署应优先使用由部署管理的环境/配置秘密;
  • allowed_users不是绑定时刻防线。由于 Connect 码在白名单之前被处理(见 8.4 节),任何持有有效码的人都能消费它——不只是白名单用户。绑定安全因此完全系于码的机密性:128 位随机、10 分钟过期、单次使用、只显示在发起用户浏览器中(从不回显到聊天)。请把 Connect 码当作一次性密码,不要转发。
  • 一个外部身份——(provider, external account, workspace/team/guild)——至多一个活跃归属者。最近一次成功绑定胜出:绑定一个已被另一 DeerFlow 用户持有的身份会转移归属权并撤销前任的绑定(连同其存储的凭证)。这在数据库层强制,两个用户竞速绑定同一身份不可能双双处于已连接状态;
  • provider bot token 仍留在channels.*永不返回给浏览器
  • 本实现不新增任何公共 provider 回调或 webhook 路由

十二、测试与源码索引:如何继续深入

上述机制的许多不变量都可以在测试中找到可验证的落点,例如:

  • 归属转移/凭证加解密/一次性码消费等仓库行为:backend/tests/test_channel_connections_repository.py
  • 浏览器 Connect API 的路由与权限:backend/tests/test_channel_connections_router.pybackend/tests/test_channel_connections_config.py
  • 各 provider 的绑定与消息处理:backend/tests/test_telegram_channel_connections.pybackend/tests/test_slack_channel_connections.pybackend/tests/test_discord_channel_connections.pybackend/tests/test_additional_channel_connections.pybackend/tests/test_buzz_channel.py
  • 过载、去重与去重后消息处理:backend/tests/test_channels.py

核心代码入口:调度与绑定闸门在 manager.py(_channel_storage_user_id_handle_chat_handle_streaming_chat_get_bound_identity_rejectionCHANNEL_CAPABILITIES);有界队列在 message_bus.py;渠道策略描述符在 run_policy.py;持久化在 channel_connections/sql.py。总体而言,"浏览器铸造一次性码 → provider worker 消费并绑定 → 数据库保证单一活跃归属者 →ChannelManager按归属者身份隔离地跑 run、存文件、回消息"这条主线清晰、独立、无回调依赖,正是它让 IM 渠道在本地与私有部署中也能拥有完整的多用户归属语义。

【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询