Skip to content

feat: 新增 Discord 渠道接入 - #82

Open
aurevian-biz wants to merge 26 commits into
OpenBMB:mainfrom
aurevian-biz:feat/discord-channel
Open

feat: 新增 Discord 渠道接入#82
aurevian-biz wants to merge 26 commits into
OpenBMB:mainfrom
aurevian-biz:feat/discord-channel

Conversation

@aurevian-biz

@aurevian-biz aurevian-biz commented Aug 7, 2026

Copy link
Copy Markdown

Summary

数字员工现在可以接入 Discord 作为第 5 个消息渠道,并具备完整的渠道能力矩阵:原生斜杠命令、线程(含自动建线程)、批量发送、历史回填、权限白名单、连接/typing 指示、语音投递、富媒体(embeds/附件)发送。此前接入渠道仅支持微信、企业微信、飞书、钉钉;本 PR 复用现有渠道无关内核(入站 staging、出站 outbox、身份服务、凭证生命周期、能力声明协议)。

实现方式

完全遵循既有渠道接入模式,无内核结构性改动;新增能力通过 ChannelCapability 能力声明协议(channel_capabilities_of 空集自动降级,存量渠道零影响)与 config_json 结构化配置(allowlist/features/batch/backfill,向后兼容)扩展:

  • 适配器backend/app/channels/adapters/discord.py):normalize / send / start_ingress / stop_ingress 协议实现 + 每绑定一线程的 DiscordStreamManager(daemon 线程 + 独立 event loop + 5s reconcile),错误分类沿用 PermanentError / TransientError 分层
  • 入站持久化service_discord_inbox.py + discord_runtime.py):stage-before-ACK 模式,(binding_id, event_id) 幂等,绑定围栏校验(channel/active/revision/account-key/白名单),envelope v2(thread_id/mention_user_ids/command)
  • 出站投递service_outbox.py + batch_service.py + typing_manager.py):payload_json 全链路传递(content/embeds/files/audio)、delivery_kind(text/voice)、TokenBucket 限流(容量 5/每 5s 补 1)、BatchJob 状态机、batch:{job_id}:{index} 幂等键、TypingManager 每 8s 触发
  • 原生斜杠命令commands.Bot + app_commands.CommandTree 注册 5 命令(/employee /switch /current /help /bind),回调序列化为文本指令走 durable inbox 管道(幂等/重试免费获得),parse_command 单一事实来源,按 guild 同步
  • 线程:入站识别 is_thread(session_id=thread_id)、出站 target.thread_id 优先、白名单按父频道;自动建线程(auto_thread 开关,默认关):群聊 reply 投递前 POST /channels/{id}/threads(type:11)建线程,线程名三级解析(metadata.thread_name → 首条用户消息 → 兜底),建线程失败自动降级主频道并标记 blocked 不重试
  • 回填fetch_history(REST 分页 limit=100)+ status=backfilled 不触发 agent + message_id 幂等 + web 会话消息页合并展示回填数据
  • 权限白名单config_json.allowlist(mode/guild/channel/user_ids + deny,deny 优先)+ 入站 fence 校验 + 拒绝落库 rejected 可审计
  • 富媒体:embeds(≤10 裁剪校验)+ 文件(≤8MiB multipart);harness 产物自动桥接:web 端「生成文件」的 workspace 产物在投递时自动读取并 base64 编码为 Discord 附件;入站附件经 attachment_bridge 复用管道
  • 连接/typing:连接状态展示(connected/reconnecting)+ 凭证失效判定;typing 经 POST /channels/{id}/typing
  • 生命周期:凭证保存端点(Bot Token 校验经 GET /users/@me)、discord:bot:{len}:{id} 外部账号键、_quiesce_binding_or_409 暂停/恢复衔接
  • 前端:渠道卡片 + DiscordSetup.tsx 配置界面 + DiscordFeatureConfig.tsx 功能配置面板(8 开关 + 白名单编辑器 + 批量发送 + 回填按钮 + 附件渲染),i18n 全量词条

关键设计决策

决策 选择 理由
能力声明 ChannelCapability StrEnum + 可选协议混入 其他渠道未实现即空集自动降级,零侵入
运行模式 线程模式(每绑定一线程 + 独立 loop) discord.py 2.x 无模块级 event loop 隐患
命令回调 序列化为文本指令走 durable inbox 幂等/重试/审计免费复用,parse_command 单一事实来源
自动建线程 建线程成功立即 commit 持久化 thread_id 重试重读短路,不重复建线程;Permanent 降级 blocked 标记
批处理 TokenBucket 整形 + 内存 BatchJob Discord 无批量端点,限流防 429;幂等键防重发
回填 status=backfilled 事件写库 天然不触发 agent;web 合并展示
白名单 deny 优先 + rejected 落库 拒绝可审计;出站不受限
语音 能力默认关闭 + ffmpeg 缺失不声明 避免虚报能力;跨线程 call_soon_threadsafe 投递
富媒体 payload_json 全链路传递 显式 channel_payload 优先,harness 产物兜底桥接

验证

  • 后端:渠道相关 pytest 全绿(test_channel_discord/test_discord_features/test_channel_outbox/test_channel_batch_*/test_channel_typing_manager/test_channel_capabilities/test_channel_harness_payload 等 150+ 新增断言);全库 1550+ passed(预存失败均为未触碰子系统);ruff 全过
  • 前端:vitest 26 files/99 tests 全绿;npm run build(tsc + vite)通过;i18n:check 通过
  • 线上实证:真实 Bot Token 配置后 connected=True,Discord 发消息 → AgentLoop 回复 delivered(群聊 + DM 双场景);harness 产物文件实际投递到 Discord 附件;真实 bug 修复(文件未投递/开关回显丢失)均经 Oracle 复核闭环
  • Oracle 设计一致性审查:无 P0;P1 全部修复闭环(出站 payload_json 传递、batch 命名参数、voice 分派、features 开关接线、回填 web 可见、降级分支同步)

测试清单

  • test_channel_discord.py:normalize/send/validate/stage 幂等/fence/stream manager 生命周期/nonce 映射
  • test_discord_features.py(+460 行):typing/白名单/线程/回填/富媒体/命令树/语音/create_thread
  • test_channel_outbox.py(+700 行):payload_json 传递/voice 分派/自动建线程矩阵/降级与幂等
  • test_channel_batch_service.py / test_channel_batch_backfill_api.py:TokenBucket/BatchJob/回填 API
  • test_channel_typing_manager.py / test_channel_capabilities.py:typing 双门禁/能力协议
  • test_channel_harness_payload.py:harness 产物→附件桥接(超限跳过/非 discord 不构造/显式优先)
  • test_discord_api.py:凭证端点(200/400/409/502/凭证不泄漏)
  • 前端:DiscordSetup.test.tsx / DiscordFeatureConfig.test.tsx(开关回显/保存透传/白名单编辑器)

风险与说明

  • 需要 Message Content Intent:未启用时群聊非 @bot 消息 content 为空(UI 有启用提示)
  • 语音能力依赖 ffmpeg:缺失时能力不声明、UI 置灰,不会虚报
  • 批处理为内存态:BatchJob 无 DB 持久化,进程重启后任务丢失(幂等键保证不重复发送);自动回填为后续演进
  • 孤儿线程风险:create_thread 成功但响应读取失败的窄窗口会重试再建(接受,Discord 允许重名)
  • 本 PR 不含真实网关集成测试(需真实 token,已线上实证)

@hm1229
hm1229 requested a review from fadeoreo August 9, 2026 07:05
@fadeoreo

Copy link
Copy Markdown
Collaborator

@aurevian-biz
你好,我注意到 PR #82 中的 commit 作者信息显示为 sunxu heli_9902@hotmail.com,这个邮箱是我的邮箱,但我并未授权在你的 Git 提交中使用它。
请不要使用他人的邮箱作为 Git author,并请尽快修改这些 commit 的 author 信息后强制更新 PR。建议使用你自己 GitHub 账号绑定的邮箱,或者使用 GitHub 提供的 noreply 邮箱。
修改后请确认 PR 中不再出现我的邮箱。谢谢。

@aurevian-biz

Copy link
Copy Markdown
Author

我没有主观故意性,应该是提交时没修改git的帐号,周四我会修改后再次提交。

@fadeoreo

Copy link
Copy Markdown
Collaborator

好的,十分感谢

@aurevian-biz

Copy link
Copy Markdown
Author

已经将作者和提交者修改完成。

为 Discord 渠道 8 项功能扩展奠定共享地基:
- ChannelCapability 枚举 + ChannelCapabilityAdapter 可选协议, 存量渠道自动降级为空能力集
- ChannelDelivery 新增 payload_json/thread_id/batch_id/delivery_kind 字段
- ChannelInboundEvent 新增 thread_id/mention_user_ids/command 字段(信封 v2)
- SQLite 就地迁移 _migrate_channel_envelope_v2_schema, 幂等补列
- channel_capabilities_of 双防御(callable+isinstance) 保证向后兼容

本地验证: pytest 9 个新测试通过, ruff 通过
按 design-discord-channel-features.md 实现全部功能:
- 原生斜杠命令: commands.Bot+CommandTree 注册 5 命令, 回调走 durable inbox 管道复用幂等/重试
- 线程: 入站识别 is_thread/thread_id, 出站 target.thread_id 优先, 白名单按父频道
- 批处理: TokenBucket 限流(容量5/每5s补1) + BatchJob 状态机 + 幂等键 batch:{job}:{index}
- 回填: fetch_history REST 分页 + status=backfilled 不触发 agent + message_id 幂等 + web 合并可见
- 权限白名单: config_json.allowlist 六重 fence 校验, 拒绝落库 rejected 可审计
- typing: send_typing(每8s) + TypingManager 三进门禁(hasattr+能力声明+features 开关)
- 语音: VOICE 默认关闭, ffmpeg 缺失自动不声明, outbox delivery_kind==voice 分派
- 富媒体: embeds(≤10裁剪)+files(≤8MiB multipart)+payload_json 全链路传递+入站附件提取

另修复真实链路缺陷: ChannelInbound dataclass 无 .get() 致回填必崩,
新增 _backfill_message_dict 归一化; features.slash_commands/typing 开关接线。

本地验证: pytest 相关集合 1511 passed(5 预存/flaky 与本次无关), ruff 通过
- DiscordFeatureConfig: 8 功能开关(features) + 白名单编辑器(mode/ID 列表) + 回填触发按钮 + 批量发送面板(轮询进度)
- ChannelMessageAttachments: 会话消息附件卡片渲染(image/pdf/文件名)
- ChannelsPage 挂载功能配置 section 与附件渲染分支
- types: ChannelAllowlistConfig/ChannelFeatureFlags 等 6 个新类型, config_json 收紧为 ChannelBindingConfigJson
- i18n: 补充 33 条 Discord 功能区词条

本地验证: build 通过, vitest 26 files/97 tests 通过
agent 运行生成的 harness 工作区文件此前只在 web 端展示(harness_artifacts
元数据),渠道投递所需的 channel_payload.files 载荷无生产写入方,导致
Discord 渠道永远只收到纯文本回复。

在 stage_channel_delivery 中对 discord 渠道补齐桥接:读取工作区文件字节
(base64),受 8MiB 单文件上限约束,读取失败静默降级;仅 discord 渠道生效,
微信/飞书/钉钉/企微路径零影响;显式 channel_payload 优先于桥接。

新增 test_channel_harness_payload.py 覆盖构造/超限跳过/非 discord 不桥接/
文件缺失降级/显式载荷优先。
此前 Web 端提问从不外投,仅 assistant 回复经 outbox 回投 Discord,
方向不对称。新增 stage_user_message_mirror 登记 kind=user_mirror
投递,接入 harness_v2_engine.run 与定时任务草稿路径;仅 web 来源
且会话完整锚定渠道时触发,幂等按 message.id 去重,失败静默不
影响 Web 主流程,渠道来源不镜像以防回声。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants