30 分钟让 AI 同事入驻 SlackOpenDots 托管渠道接入一行服务端代码都不用写【免费下载链接】OpenDotsYour always-on AI coworkers that move between text, calls, and Slack.项目地址: https://gitcode.com/gh_mirrors/op/OpenDots把 AI 智能体接进 Slack绝大多数团队的第一反应是又得搭一台 Webhook 服务器、维护 Bot Token、签 Slack App Manifest还得处理事件重放和 Socket Mode。OpenDots 给出的答案是另一条路应用侧一行服务端代码都不用写通过两条 CLI 命令 四行环境变量就能让一个专职 AI 同事Dot在 Slack 里被 之后开始工作并且只在你的工作区、只有你允许的用户能叫得动它。这篇文章不打算停留在照抄文档层面而是结合 docs/SETUP.md 的官方接入流程与仓库源码src/server/slack-channel.ts、src/server/platform.ts 等把托管渠道这个架构取舍、双重白名单的代码实现、线程级对话保持的机制逐一拆开。读完你不仅能 30 分钟完成接入还能在出问题时知道该查哪里。为什么是托管渠道把最脆弱的环节外置传统 Slack 接入的麻烦不在对话逻辑而在基础设施你要自己起一个服务接收 Slack 事件回调自己校验签名自己处理 Socket Mode 连接还要把 Bot Token 和 Signing Secret 小心翼翼地藏起来。OpenDots 没有选择这条路。在 docs/SETUP.md 的 Slack 一节官方明确写道OpenDots 使用copilotkit/channels建立托管 Slack 连接应用只负责声明渠道和对应的专家 AgentSlack 适配器与消息投递由 CopilotKit Intelligence 管理你不需要单独维护一个 Slack Webhook 服务器或 Socket Mode 连接。这个分工在架构上非常清晰仓库 README 的架构图README.md展示了完整链路Slack -- Managed channel connection -- Channels SDK -- Specialist compute agents也就是说事件接收、签名校验、消息投递这些容易写错且写错了很危险的部分被托管在 Intelligence 一侧OpenDots 服务端只做一件事声明一个渠道并把它绑定到某个 Dot 的 Agent 上。源码里这一步落在 src/server/platform.ts 的构造函数中——当.env里同时配齐了SLACK_CHANNEL_NAME、SLACK_TEAM_ID、SLACK_USER_IDS时才调用createSlackChannel把渠道注册进运行时if (config.slackChannel config.slackTeam config.slackUsers.length) { const dotId config.slackDotId ?? workspace.dots()[0].id; const slack createSlackChannel({ name: config.slackChannel, config, ownerId: workspace.ownerId, paused: () store.settings().paused, agent: () new DotAgent(store, workspace, config, dotId, true, this.setupTelemetry), }); channels.push(slack); }注意第 4 个参数true——它标记这是一个渠道回合channel turn后面会看到它如何影响工具权限与错误处理。SLACK_DOT_ID缺省时Slack 里的 AI 同事默认就是工作区的初始 Dot。30 分钟实操两条 CLI 命令 四行环境变量整个接入可以分成三段每段都是声明式操作不涉及手写任何服务端代码。第一步确定 Intelligence 项目并创建托管渠道。在 OpenDots 目录下npm ci cp .env.example .env之后执行npx --yes copilotkitlatest project select npx --yes copilotkitlatest channels add --name opendots --display-name OpenDots --adapter slack --jsonproject select会写入项目级密钥CPK_INTELLIGENCE_API_KEYchannels add则返回一个nextAction引导你完成三件事用 CLI 生成的 Manifest 在 Slack 创建 Bot App、通过托管设置流程提交凭据、然后运行它给出的resumeCommand。官方特别强调保持 Socket Mode 关闭——因为适配器侧的连接是托管维护的你不再需要它。若返回blocked说明 Slack 控制台还有一步没做完返回failed则必须先解决再继续。第二步在.env里填四行配置。参考 .env.example 中 Slack 一节的注释SLACK_CHANNEL_NAMEopendots SLACK_TEAM_IDT_REPLACE_WITH_WORKSPACE_ID SLACK_USER_IDSU_REPLACE_WITH_YOUR_USER_ID SLACK_DOT_IDREPLACE_WITH_DOT_ID这里有个容易踩的坑SLACK_CHANNEL_NAME必须精确匹配托管渠道的声明名opendots而不是 Slack 里的会话名#general。SLACK_USER_IDS支持逗号分隔多个用户SLACK_DOT_ID如果省略默认绑定初始 Dot。第三步重启并在 Settings 里确认激活。环境变量改动后必须重启服务。此时在设置页能看到 Slack 状态为online激活完成前不要急着发消息。整个流程里Bot Token 与 Signing Secret 始终留在托管适配器的配置中既不进.env也不进浏览器应用侧拿不到也不需要拿到。workspace user 双重白名单身份校验怎么写进了代码托管渠道听起来像是把安全也一起外包了但 OpenDots 并没有这么做——准入规则完全由应用侧强制执行。它要求你在.env里同时声明工作区SLACK_TEAM_ID和用户SLACK_USER_IDS缺一不可在 src/server/platform-config.ts 的setupStatus中只有三者齐备才算declaredSlack否则 Slack 状态只能是setup_required或not_configured。真正的校验在 src/server/slack-channel.ts 里有两层。第一层是渠道级别的身份映射slackIdentity它决定一个 Slack 消息能否被识别为所有者export function slackIdentity(context, config, ownerId) { if ( context.provider ! slack || context.tenant.id ! config.slackTeam || context.actor.kind ! human || !config.slackUsers.includes(context.actor.id) ) return null; return { id: ownerId, name: OpenDots owner }; }四个条件缺一不可必须是 Slack 渠道、租户 ID 必须等于白名单里的工作区、参与者必须是人类机器人/应用消息直接排除、用户 ID 必须在白名单内。只要有一条不满足就返回null这条消息根本不会被当成所有者消息。第二层是消息级别的eligible过滤作用于每一条 mention 和 follow-upmessage.platform slack message.user?.id options.ownerId message.actor.kind human options.config.slackUsers.includes(message.actor.id) (message.operation?.kind ?? created) created注意这里连operation.kind都校验了编辑过的消息、删除的消息不会触发任何 Agent 运行。这与仓库的安全原则一致——SECURITY.md 明确写道显式映射 Slack 角色绝不要把显示名或客户端提供的用户 ID 当作身份证明。仓库配套的测试 tests/slack-channel.test.ts 逐一验证了这些边界错误的工作区、白名单外的用户、非 human 参与者、非 Slack 渠道统统返回null且不触发任何副作用。还有一个设计要点值得注意这套模型是单所有者的——白名单用户会被统一映射到唯一的 OpenDots owner 身份。回复内容对 Slack 会话的所有可见者可见所以官方建议在选择 Dot 的 Space 访问权限与工具权限时要按对话可能被更多人看到来保守配置。这不是一个多用户身份模型。线程级对话保持 一次订阅之后只认这个线程接入 Slack 之后最怕的是每次 都像新对话或任何消息都触发 Agent。OpenDots 的机制是线程级订阅你第一次 Bot 之后它subscribe这条线程此后只有这条线程内的消息才会触发运行其他线程、无关频道一概不理。看 src/server/slack-channel.ts 里slackHandlers的实现mention被 时校验eligible→ 尝试subscribe()订阅失败也要回答但会记录失败原因→runAgent()message线程内 follow-up校验eligible→ 查isSubscribed()→ 只有已订阅才runAgent()。仓库的测试用一句话概括了这个行为answers an allowed mention once, then only subscribed follow-ups——首次 只运行一次后续线程消息只有在已订阅状态下才继续回答。与此同时src/server/dot-agent.ts 会把 Slack 线程自动绑定为一条持久对话if (this.channel !this.workspace.conversations() .some((thread) thread.id input.threadId)) this.workspace.bindThread(input.threadId, dot.id, Slack conversation);这条线程被绑定到指定的 Dot 与所有者后续的 follow-up 都复用同一个 Intelligence Thread——上下文、记忆、工具权限全部延续。而线程绑定之外的对话比如用户拉起的另一个话题、机器人事件、编辑与删除操作、白名单外的用户都不会启动 Agent 运行。如果你想暂停 AI 同事paused状态会让它回复一条OpenDots is paused的提示而不是继续工作。零自建 Webhook 背后的工程细节错误脱敏与审批边界托管渠道省掉的不只是基础设施还有一类很隐蔽的坑provider 返回的错误信息里可能夹带凭据。OpenDots 对此做了显式防护。src/server/slack-channel.ts 顶部有一行注释Never pass provider messages/stacks to logs or Slack: they can contain credentials并提供了safeFailure——它只保留错误类型名和 HTTP 状态码如Error (HTTP 401)把原始 message 与堆栈全部丢弃tests/slack-channel.test.ts 专门验证了含 SECRET 的 provider 错误绝不会出现在日志或 Slack 回复中。另一个取舍是审批工具不出渠道。docs/CONNECTIONS.md 明确说明MCP 连接的审批卡片只出现在 Web 端经由 Slack 触发的回合里遇到Ask first工具Agent 会引导你回 Web 应用继续审批。对应的代码在 src/server/dot-agent.ts渠道回合直接不注入pageReviewTool与connectionActionTool这两个客户端审批工具const clientTools this.channel ? [] : ...同时把运行失败统一包装成不含内部细节的channelError()。这保证了 Slack 是一个只读触发、遇到敏感操作转回 Web的入口而不是绕过人工审核的后门。上线前的验证清单官方在 docs/SETUP.md 给出了完整的验收步骤接入完成后按顺序过一遍即可用白名单用户 Bot确认在同一线程内得到回复在该线程内继续回复确认上下文连续线程级保持生效开一个无关线程、用一个未授权用户分别尝试确认不会触发运行在 OpenDots 中暂停助手再发一条允许的消息确认收到 paused 提示回 Settings 检查渠道连接状态确认没有activation_failed。30 分钟的承诺并不夸张渠道创建是两条 CLI 命令应用侧配置是四行环境变量剩下的识别谁在说话、保持哪个线程的上下文、失败时不泄漏凭据都已经在 src/server/slack-channel.ts 的slackIdentity/slackHandlers里替你写好了。托管渠道把最危险的凭据管理与事件投递外置应用侧只保留声明与白名单——这正是 OpenDots 在Slack 接入这件事上敢说一行服务端代码都不用写的底气所在。【免费下载链接】OpenDotsYour always-on AI coworkers that move between text, calls, and Slack.项目地址: https://gitcode.com/gh_mirrors/op/OpenDots创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考