Munder Difflin 从 Slack 驱动 AI Agent Hive:完整接入指南与源码级原理
【免费下载链接】munder-difflinA local multi-agent harness that works with your existing Claude Code, Codex subscriptions, allows you to run an office of agents项目地址: https://gitcode.com/GitHub_Trending/mu/munder-difflin
Munder Difflin 是一套运行在你本机的多智能体编排工具(multi-agent harness),它把一组 AI agent 组织成一座"虚拟办公室",日常主要通过桌面端操作。而 Slack 集成则把这座办公室的入口延伸到聊天工具里:在频道中@mention 机器人,消息就会变成 hive 队列中的一项任务,由编排器分派给合适的 agent 执行,任务完成后再把摘要回复到同一线程。本文基于仓库中的实战文档 run-ai-agent-hive-from-slack-setup.md,完整拆解这套集成从 Slack 应用创建、凭据配置、Event Subscriptions 对接,到消息触发、线程激活、附件摄入、完成摘要回帖的完整链路,并深入到 slack.ts、slack-trigger.cjs 等源码,讲清"为什么这样设计"。
版本提示:文档写作时对应 0.5.2 之前的接入方式。从 Munder Difflin 0.5.2 起,Slack 连接不再依赖公共 URL,设置字段也已移动,请以 connect-slack-to-munder-difflin.md 中描述的当前配置流程为准。本文完整保留了旧版接入的机制细节,这些底层行为(触发判定、线程激活、安全模型)在新版本中仍然一致,仍具参考价值。
你正在构建什么
一条在已连接的 Slack 频道中@mention 机器人的消息,会变成编排器队列中的一个任务——和你直接在应用里输入的任务完全等价。编排器(GOD/Michael)收到后对其进行分诊,并路由给最合适的 agent。以下两个设计让整个交互像对话而非"发完就忘":
- 线程激活(Thread activation):线程内的第一次 @mention 会"激活"该线程。之后在同一线程里继续回复时无需再次 @mention 机器人,hive 会持续监听。这是真正的来回对话,不是一次性命令。
- 完成摘要(Done-summaries):任务落地后,办公室通过 Slack 的
chat.postMessage把摘要回复回原线程。你在哪里提问,就在哪里看到结果。
它还支持文件与图片附件摄入:在 @mention 的同时附带截图或日志文件,hive 会把它下载下来交给 agent 使用(源码中单文件上限为 10 MB,见 index.ts 的SLACK_FILE_MAX_BYTES)。
底层实现没有任何@slack/bolt、没有框架、也没有云端托管。应用内部只有一个极简的node:http服务器,实现了 SlackEvents API中恰好够用的那部分协议;本地隧道(tunnelmole)则为 Slack 提供一扇可以"按门铃"的公网入口。你的 agent、它们的记忆、你的代码始终留在本机(实现见 slack.ts 的模块注释)。
整体流程如下:Slack 事件 → 隧道 → 本地 webhook 服务器(HMAC 验签)→ 触发判定(mention/线程/附件)→ 去重 → 消息入队 → 编排器分诊 → agent 执行 → 线程内回帖 / done 摘要。
Part 1 — 创建 Slack 应用
进入 Slack 的应用管理控制台,选择**从零创建(From scratch)**一个新应用,放入机器人要工作的 workspace。整个接入过程你需要收集两个密钥、配置三个权限范围,下面是每个配置项的确切位置。
Signing Secret(签名密钥)
在应用的左侧导航打开Basic Information,找到App Credentials区块,复制Signing Secret。Munder Difflin 用它来验证每一个入站请求确实来自 Slack——它不会离开你的机器,也绝不会被写入日志。
Bot Token Scopes(机器人令牌权限)
打开OAuth & Permissions → Scopes → Bot Token Scopes,添加以下三个权限:
| Scope | 作用 |
|---|---|
chat:write | 允许机器人发帖回复,包括把完成摘要写回线程 |
channels:history | 让 Slack 把公开频道中的消息与线程回复事件投递过来 |
groups:history | 同样的能力,针对私密频道 |
只需这三个,不需要更多。hive 会从每个事件中自动学习机器人自己的 user id,因此检测 @mention 不需要额外申请权限范围——对应实现见 slack.ts:服务端从payload.authorizations[0].user_id首次获取自己的botUserId,之后用它在文本中匹配<@BOTID>形式的提及(shouldTrigger的判定逻辑在 slack-trigger.cjs)。
安装应用并获取 Bot User OAuth Token
仍在OAuth & Permissions页面,点击Install to Workspace并批准授权。Slack 随后会展示一个以xoxb-开头的Bot User OAuth Token,复制它。该令牌授权机器人发出回复;与签名密钥一样,它只存在于应用主进程的配置中,从不被记录到日志。回复时携带它的位置见 slack.ts:直接以Bearer xoxb-…请求slack.com/api/chat.postMessage,同样是零 SDK 的裸node:httpsPOST。
把两个值都收好:接下来要先把它们粘贴进 Munder Difflin,之后还要回来完成 Event Subscriptions 配置。
Part 2 — 配置 Munder Difflin
打开 Munder Difflin,进入Settings,在 Slack 区域按顺序完成:
- Enable Slack(启用集成)。
- 粘贴Signing Secret。
- 粘贴Bot User OAuth Token(
xoxb-…)。 - (可选)设置Channel ID把摄入范围限制到单一频道;留空则接受机器人所在的所有频道。这是避免嘈杂 workspace 变成嘈杂 hive 最干净的办法。
- (可选)如果本机3847端口(默认值)已被占用,可以修改端口。
点击启动后,应用会绑定本地 webhook 服务器并打开公共隧道,随后显示一个Request URL——这是 Slack 将要调用的公网地址。复制它,供最后一步使用。
如果隧道无法启动,应用会给出真实错误,而不是丢给你一个失效的 URL。本地处理程序是安全边界,因此即使隧道失败它也保持运行——但要让 Slack 能访问到它,你确实需要一个可用的隧道 URL。
配置项与源码对照:以上五个设置项在 config.ts 中都有对应字段——slackEnabled(总开关,默认关闭)、slackSigningSecret、slackBotToken、slackChannelId(留空即任意频道)、slackPort(默认端口回退逻辑见 index.ts 的cfg.slackPort && cfg.slackPort > 0 ? cfg.slackPort : 3847)。启动入口startSlackServer()位于 index.ts:先构建SlackWebhookServer,成功启动隧道后再拉起两个附属组件——token 门禁的回环回复端点SlackReplyServer,以及监听 kanban 中 Slack 来源任务完成状态的 done 通知轮询器。
Part 3 — 把 Slack 指向你的 Request URL
回到 Slack 应用,打开Event Subscriptions,打开Enable Events开关。
把 Munder Difflin 提供的Request URL粘贴进Request URL字段。Slack 会立即向它发起一次性的url_verification握手;应用回应 challenge 后,Slack 显示绿色Verified。(如果验证失败,参考下方故障排查——绝大多数情况都是隧道的问题。)
随后滚动到Subscribe to bot events添加:
app_mention—— 当有人 @mention 机器人时触发。这是主触发器。message.channels—— 公开频道中的消息事件(用于已激活线程内的回复和file_share 上传)。message.groups—— 同样的作用,针对私密频道。
保存更改。Slack 可能提示需要重新安装应用以应用新的事件订阅——按提示操作即可。
整个闭环就完成了。url_verification握手的处理代码在 slack.ts:收到type === 'url_verification'的载荷后,原样把challenge以 JSON 回写,状态码 200。
触发规则:什么会触发,什么被忽略
hive 会在以下情况触发:
- 在已连接的频道中直接@mention机器人;
- 在机器人已被 @mention 过的线程内发出任意回复(无需再次提及);
- 上传文件或图片且同时 @mention 了机器人。
它刻意忽略机器人自己的帖子、消息编辑、频道加入通知以及所有其他消息子类型——因此它绝不会自说自话,也不会对频道噪音做出反应。
这套判定逻辑完全封装在 slack-trigger.cjs 的shouldTrigger()里,可以概括为四道闸门:
- 频道过滤:配置了
channelId时,其他频道的事件直接丢弃(L117-L118)。 - 自循环 + 子类型安全:
ev.bot_id存在(机器人自己的帖子)或子类型不是file_share的一律不触发(L123-L125);file_share是唯一放行的子类型。 - 触发判定:
app_mention事件类型、文本中包含<@botUserId>、或处于已激活线程——三者满足其一即触发(L130-L138)。 - 线程激活:命中 mention 时,把
thread_ts(若 mention 本身是回复)或消息自身ts记入激活线程集合(L142-L145)。
两个配套的数据结构值得注意:ActivatedThreads是有界 FIFO 集合,最多记住500个线程根(ACTIVATED_THREADS_MAX,L14),防止长期运行的机器人内存无限增长;SeenEvents是幂等缓存,同样上限 500 条,配合dedupKey(channel:ts复合键,L95-L100)保证同一条逻辑消息只触发一次——当应用同时订阅app_mention和message.*时,一次 @mention 会以两种事件类型各投递一次,二者共享channel:ts但拥有不同的event_id,因此只有channel:ts才能可靠去重(L84-L94)。文件摄入同样有上限:单条消息最多提取10个文件(MAX_FILES_PER_MESSAGE,L16),且只保留带有可下载url_private的条目(L149-L158)。
这些边界行为都被 slack.test.cjs 的测试用例逐一锁死:普通消息不触发、mention 触发并激活线程、线程内回复免提及触发、机器人自回复不回环、message_changed子类型屏蔽、错误频道过滤、文件提取与封顶、双订阅去重折叠成一次投递等等(参见该文件的 Case 1–8 与集成用例)。
试运行
在机器人已加入的频道里输入类似指令:
@YourBot summarize the open PRs in the api repo and flag anything stale
消息进入队列,编排器完成路由,一名 agent 接手任务。在同一线程里继续回复即可保持对话——hive 仍在监听。任务完成后,摘要会直接出现在该线程中。
值得补充的是 Slack 来源请求的"幕后编排协议":主进程会为每条 Slack 消息预置一段AUTONOMOUS REQUEST PROTOCOL(构造逻辑见 index.ts),注入编排器的指令中,要点包括:快速路由到最相关的现有 agent;把精确的回帖命令交给 agent,让其在完成时用md-slack-reply.cjs把实质性结果直接发回本线程;不提问交互式问题,只对推送到 main/远端、购买基础设施、删除未由它创建的仓库等高风险动作暂停等待批准;回帖必须是实质性的 Slack-mrkdwn 答案而非一句"done"。这解释了为什么 Slack 请求体验起来是"对话式"的。
故障排查:Request URL 会在重启后轮换
这是最值得记住的一个怪癖:公网 Request URL 来自临时隧道(tunnelmole),每次应用重启都会变化。所以当 Slack 突然不再触发 agent——昨天验证还是绿的、今天却一片寂静——几乎可以断定是应用被重启过、旧 URL 已失效。
修复很快:
- 停止并重新启动Munder Difflin 的 Slack 集成(或直接重启应用)。
- 复制它显示的新Request URL。
- 把它重新粘贴到 Slack 的Event Subscriptions → Request URL,等待重新验证。
Slack 一显示Verified,触发立即恢复。如果你经常遇到这种情况,养成每次重启后都检查 Request URL 的习惯——这是 Slack 触发"失灵"最常见的原因。
隧道实现对应 slack.ts 的openTunnel():动态import('tunnelmole')(因为它是 ESM-only 包,主进程是 CJS 打包,必须动态导入),带 10 秒超时;隧道是 best-effort 的——本地 handler 作为安全边界始终在线,即使隧道建立失败也只是报错而不是关停服务(L139-L164 的start()流程)。
为什么这样构建:安全模型与本地优先
把它托管在云函数里会更省事,但做成本地 webhook 正是关键点。安全模型在 slack.ts 的verify()中清晰可见:
- HMAC 验签:用签名密钥对
v0:<ts>:<rawBody>计算 HMAC-SHA256,与X-Slack-Signature头做常数时间比较(timingSafeEqual); - 重放防护:
X-Slack-Request-Timestamp与当前时间偏差超过5 分钟即拒绝(REPLAY_WINDOW_SECONDS,L108); - 请求体上限:事件载荷超过1 MB直接 413 断连,防止未认证对端在验签前就耗尽内存(
MAX_BODY_BYTES,L106); - 任何验签失败返回 403,且验签发生在解析 JSON 之前,顺序见
handleBody()(L227-L229)。
两个密钥——签名密钥和机器人令牌——只存在于你的机器上。Slack 仅仅是"启动与观察"工作的薄远程面;办公室本身(agent、记忆、git 历史)从不离开你的电脑。这正是 local-first-ai-agent-orchestration.md 所阐述的本地优先哲学:把控制面留在本机,让外部世界礼貌地敲门。
另一个安全细节是回帖令牌的隔离:SlackReplyServer(slack.ts)只绑定127.0.0.1回环地址、从不被隧道转发,且要求每次请求携带x-md-reply-token会话密钥(双重防护,即使回环绑定也拒绝非回环对端);机器人令牌通过惰性 getter 在主进程内读取,agent 拿到的只是{ port, token }连接信息与"回复指令",永远不会被直接授予令牌。主进程把该端点的{ port, token }以 0600 权限写入 userData 下的slack-reply.json供辅助脚本发现(index.ts)。
完成摘要的"恰好一次"也由主进程保证:startSlackDoneObserver()每 5 秒轮询一次共享 kanban(hive/tasks.json),只对 Slack 来源且实时发生done 转态的任务发一条摘要(已存在即 done 的任务不补发);slack-done-notified.json持久化已通知 ID,重启后依然幂等;若 agent 已通过回环/reply端点直接回帖,directlyRepliedThreads集合会让轮询器跳过该线程——轮询器只是兜底,不会重复发帖(index.ts)。
FAQ
如何把 Slack 连接到 Munder Difflin?创建一个 Slack 应用,添加chat:write、channels:history、groups:history三个 bot scope,安装应用,然后把 Signing Secret 与xoxb-Bot User OAuth Token 粘贴进 Settings。把应用的 Request URL 复制进 Slack 的 Event Subscriptions,订阅app_mention+ message 事件,@mention 机器人即可开工。
hive 需要哪些 Slack 权限?只需要三个:chat:write用于回复与完成摘要,channels:history和groups:history让 Slack 投递 hive 监听的消息与线程回复事件。机器人的 user id 由应用自动从事件中学习,无需额外权限。
为什么 Slack 停止触发了?Request URL 来自临时隧道,重启后轮换。重启应用、复制新 URL、重新粘贴进 Event Subscriptions 即可恢复。
hive 会回应私密频道的消息吗?会——前提是机器人被邀请进该私密频道,并且你配置了groups:historyscope(若设置了 Channel ID 过滤,则该频道需与配置一致)。
agent 能拿到机器人令牌吗?不能。agent 通过回环/reply端点回帖,令牌始终留在主进程内;不过令牌仍保存在本机配置文件里,拥有完整文件访问权限的 agent 理论上仍可读取,配置时需留意这一点。
从 Slack 线程到本地 AI 办公室的"远程遥控器",Munder Difflin 让这种体验在安全边界内成立:HMAC 验签在边缘完成、消息像其他任务一样入队、结果回帖在提问处呈现。配合 scheduling-autonomous-agent-missions.md 中的定时任务,画面就完整了:定时器把周期性工作放入队列,Slack 把临时工作放入队列,你的办公室同时运转两者——无论你是否在盯着看。
【免费下载链接】munder-difflinA local multi-agent harness that works with your existing Claude Code, Codex subscriptions, allows you to run an office of agents项目地址: https://gitcode.com/GitHub_Trending/mu/munder-difflin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考