首页
/ Slack × Claude Managed Agents:用无状态 Webhook 桥接器把 CMA Agent 接入 Slack 线程

Slack × Claude Managed Agents:用无状态 Webhook 桥接器把 CMA Agent 接入 Slack 线程

2026-09-07 17:04:27作者:凌朦慧Richard

在 Slack 里 @ 提及一个 Claude Managed Agent(CMA),并让 Agent 的回复直接出现在对应线程(thread)中——这是本指南围绕的核心场景。本指南以仓库中 managed_agents/slack 目录下的 README 为骨架,逐层展开它的架构设计(Slack 事件 + Anthropic 会话回调两条单向链路)、sessions.create 携带的 metadata 路由机制,以及从本地快速启动到生产化改造的完整实操步骤,同时结合 skill.md 与各源码文件讲解其中的关键陷阱与排查手段。读完你将能独立搭建一个"Slack 消息进、Agent 回复出"的无状态桥接服务,并具备自行调试与扩展的能力。

整体架构:两个 webhook、一条无状态桥

仓库中 skill.md 将这套系统概括为"one bridge, two webhooks"。它与常见的"双向长连接 Agent"完全不同:Claude 会话在 Anthropic 基础设施上异步运行,因此 Slack 与桥接服务之间、Anthropic 与桥接服务之间各有一条单向信号链路:

Slack @mention ──▶ /slack/events ──▶ sessions.create (+ metadata) ──▶ 200
                                              │
                            Claude runs to idle on Anthropic infra
                                              │
/cma-webhook ◀── session.status_idled ◀───────┘
      │
      └──▶ sessions.retrieve → read metadata → chat.postMessage

理解这套设计要抓住三个要点:

  1. Slack → 桥/slack/events)在用户 @ 机器人或私聊时触发。事件载荷携带 channeltsthread_tsusertext 等上下文。
  2. Anthropic → 桥/cma-webhook)在 CMA 会话转入 idle(空闲)或 terminated(终止)时触发。载荷只携带 CMA session ID
  3. 两条链路都不携带 Agent 的输出。它们只是携带 ID 的"门铃信号",真正的数据(metadata、回复文本)需要桥接器主动拉取。

webhook 是门铃,不是投递员

Anthropic 的 session.status_idled 载荷被刻意设计得很薄——本质上只有 {type, id}。收到信号后,桥接器需要主动调用 sessions.retrieve(id) 拿回 metadata,再通过 sessions.events.list(id) 拉取输出。即"推送信号,拉取数据"。这一设计体现在 src/cma-webhook.ts 的处理逻辑中。

metadata 就是全部的路由状态

这是整套设计的灵魂。在 kickoff 阶段,桥接器把 Slack 路由信息写入 CMA 会话:

const session = await anthropic.beta.sessions.create({
  agent: CLAUDE_AGENT_ID,
  environment_id: CLAUDE_ENVIRONMENT_ID,
  metadata: {
    slack_channel: m.channel,
    slack_thread_ts: m.thread_ts,
    slack_team: m.team,
  },
});

代码见 src/agent.ts。当若干秒后 idle webhook 到达、载荷里只有 session ID 时,桥接器调用 sessions.retrieve(id),从返回的 session.metadata 中读回 slack_channelslack_thread_ts,再用 chat.postMessage 精确投递到原始线程。桥接器自身不保存任何会话状态,这就是它无状态的原因。

面向 Slack 3 秒确认窗口的取舍

Slack 对任何未在 3 秒内返回 2xx 的事件会进行重试。桥接器必须在 CMA 会话结束前返回,因此处理流程被设计为:验证签名 → 按 event_id 去重 → 不 await kickoffAgentSession()(fire-and-forget)→ 立即返回 204。这一时序约束在 src/slack-events.ts 中有完整体现。

目录结构与依赖概览

managed_agents/slack/README.md 给出了清晰的模块划分:

文件 职责
setup/create-agent.ts 一次性操作:agents.create + environments.create,打印 ID
src/main.ts Bun 服务入口与路由分发
src/slack-events.ts 校验 Slack 签名、处理 url_verification、fire-and-forget 启动会话
src/agent.ts sessions.create + user.message,携带路由 metadata
src/cma-webhook.ts beta.webhooks.unwrap → 按 metadata 过滤 → chat.postMessage
skill.md 安装走查、常见陷阱与调试指引

依赖方面,package.json 声明了 @anthropic-ai/sdk(要求 ≥ 0.95.1,因为 beta.webhooks.unwrap 与 CMA Beta API 需要该版本)与 @slack/web-api(用于 chat.postMessage)。完整的依赖与 npm scripts(dev / start / setup)见 managed_agents/slack/package.json

服务入口与环境变量检查

src/main.ts 使用 Bun.serve 暴露三个端点:

  • GET /{ status: "ok" } 健康检查;
  • POST /slack/events → 转发给 handleSlackEvents
  • POST /cma-webhook → 转发给 handleCmaWebhook

服务启动时会强制校验五个环境变量,任一缺失即 FATAL 退出:

const PORT = Number(process.env.PORT) || 3000;
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;

for (const v of [
  "SLACK_SIGNING_SECRET",
  "SLACK_BOT_TOKEN",
  "ANTHROPIC_WEBHOOK_SIGNING_KEY",
  "CLAUDE_AGENT_ID",
  "CLAUDE_ENVIRONMENT_ID",
]) {
  if (!process.env[v]) {
    console.error(`FATAL: ${v} is required`);
    process.exit(1);
  }
}

其中 SLACK_SIGNING_SECRET / SLACK_BOT_TOKEN 来自 Slack 应用,ANTHROPIC_WEBHOOK_SIGNING_KEY 来自 Anthropic Console 的 Webhooks 配置,CLAUDE_AGENT_ID / CLAUDE_ENVIRONMENT_ID 来自 bun run setup 的一次性创建脚本。

从零配置的完整流程

一次性创建 Agent 与 Environment

首次使用前需要运行 setup/create-agent.ts,它调用 Beta API 分别创建 cloud 环境与 Agent,并把两个 ID 打印到终端供写入 .env.local

const env = await anthropic.beta.environments.create({
  name: `slack-bridge-${Date.now()}`,
  config: { type: "cloud", networking: { type: "unrestricted" } },
});

const agent = await anthropic.beta.agents.create({
  name: "Slack Assistant",
  model: "claude-opus-4-7",
  system:
    "You are a helpful assistant embedded in Slack. Keep replies concise and conversational — they are posted as thread replies. Use plain text or Slack mrkdwn (e.g. *bold*, `code`); avoid Markdown headers.",
  tools: [{ type: "agent_toolset_20260401", default_config: { enabled: true } }],
});

console.log("\nAdd to .env.local:");
console.log(`CLAUDE_ENVIRONMENT_ID=${env.id}`);
console.log(`CLAUDE_AGENT_ID=${agent.id}`);

注意系统提示词专门针对 Slack 场景做了约束:回复保持简短、口语化,因为会作为线程回复展示;使用纯文本或 Slack mrkdwn 语法而非 Markdown 标题。agent_toolset_20260401 是该仓库所用 SDK 版本下的 Agent 工具集类型标识,default_config.enabled: true 表示默认启用。

本地开发检查清单

skill.md 的 Local dev checklist 顺序执行(顺序决定成败):

  1. ngrok http 3000 → 记录公共 URL(后续两个 webhook 的 Request URL 都要用它)。
  2. bun run setup → 把 CLAUDE_AGENT_ID / CLAUDE_ENVIRONMENT_ID 复制进 .env.local之后再粘贴 Slack 密钥时不要覆盖它们
  3. Slack App → OAuth & Permissions → Bot Token Scopes 添加 app_mentions:readchat:write(若需私聊还加 im:history)→ Install to Workspace → 复制 xoxb-… 写入 SLACK_BOT_TOKEN
  4. Slack App → Basic Information → 复制 Signing SecretSLACK_SIGNING_SECRET
  5. Anthropic Console → Manage → Webhooks:填入 <url>/cma-webhook,订阅 session.status_idled + session.status_terminated → 复制 whsec_…ANTHROPIC_WEBHOOK_SIGNING_KEY必须与 API key 处于同一 workspace
  6. bun run dev — 服务必须在步骤 7 之前启动。
  7. Slack App → Event Subscriptions → 开启 → Request URL 填 <url>/slack/events → 显示 Verified ✓ → 添加 bot 事件 app_mentionSave Changes → 按提示重装应用。
  8. 在 Slack 中 /invite @your-bot 拉进频道,然后 @your-bot hello 验证。

项目入口文档建议用更简洁的方式:在目录下执行三条命令后,直接问 Claude "walk me through setting this up",由 Claude 读取 skill.md 并按照真正能跑通的顺序驱动整个配置过程:

cd managed_agents/slack
bun install
claude

两个端点各自的请求处理

Slack 事件端点src/slack-events.ts)的处理顺序是:

  1. 读取原始 body 并做签名校验:计算 v0: + timestamp + rawBody 的 HMAC-SHA256 并与 x-slack-signaturetimingSafeEqual 常量时间比较,同时校验时间戳在 ±5 分钟容差内(TOLERANCE_SEC = 5 * 60),失败返回 401;
  2. payload.type === "url_verification",原样返回 challenge 完成 URL 验证;
  3. event_callback 直接 204;
  4. event_id 做幂等去重(Slack 重试会复用同一 event_id);
  5. 判定 app_mention(频道 @ 提及)或 im 私聊且非 bot 回声且有文本;
  6. stripMention 去掉文本中的 <@...> 提及前缀;
  7. fire-and-forget 调用 kickoffAgentSession.catch 记录错误)并立即 204。

Anthropic webhook 端点src/cma-webhook.ts)的处理顺序是:

  1. anthropic.beta.webhooks.unwrap(rawBody, { headers: Object.fromEntries(req.headers) }) 完成 HMAC 签名验证与解析。注意 unwrap() 需要普通字符串映射表而非 fetch 的 Headers 对象,因此这里做了 Object.fromEntries 转换(@anthropic-ai/sdk ≥ 0.95.1 才支持);
  2. 按顶层 event.id 幂等去重(Anthropic 重试会复用同一 event.id);
  3. 只处理 session.status_idledsession.status_terminated 两种类型;
  4. retrieve-then-filter:先 sessions.retrieve(id)(任何异常一律 204 静默丢弃),再检查 metadata 中是否存在 slack_channel / slack_thread_ts,没有就 204 退出——过滤必须在调用 events.list 之前完成;
  5. terminated 事件向原线程发一条警告消息;
  6. 正常 idle:用 for await 迭代 sessions.events.list(id)(分页对象自动翻页),收集所有 agent.message 事件中的 text block,拼接后通过 slack.chat.postMessage 回帖到线程。

新手最容易踩的坑(Gotchas)

skill.md 整理了文档里不明显、却最耗时的一批坑,配置时请逐条核对。

用开发者沙箱而不是公司工作区

Slack 的 Developer Program Sandboxes 提供免费的 Enterprise Grid 测试组织:有管理员权限、假用户/假频道,不会把半成品 bot 装进生产环境。注意这不是 "agent quickstart",而是 Developer Program → Sandboxes。流程:加入 Slack Developer Program → 邮件激活 → 接受条款 → 在面板 Provision Sandbox(可选空沙箱或预置假用户/频道)→ 从邮件邀请完成设置(你成为 Primary Org Owner)→ 在沙箱内创建至少一个 workspace → 把应用建在沙箱 workspace 上开发。

OAuth scope ≠ 事件订阅

OAuth & Permissions → Bot Token Scopes 里加 app_mentions:read 只是授予了"看到提及"的权限不会让 Slack 投递事件。还必须去 Event Subscriptions 页面打开开关、填 Request URL,并在 "Subscribe to bot events" 里添加 app_mention。这是两个独立页面,漏掉第二个会"零投递且没有任何报错"。

xoxb-xapp-:两个 token、两个页面,极易拿错

Token 前缀 所在页面 用途
Bot User OAuth Token xoxb- OAuth & Permissions(安装后出现) chat.postMessage —— 这才是你要的
App-Level Token xapp- Basic Information → App-Level Tokens 仅 Socket Mode WebSocket 使用

xapp- token 调 chat.postMessage 会以 invalid_auth 失败。xoxb- token 只有在你至少添加了一个 bot scope 并且点击了 Install/Reinstall to Workspace 之后才会出现。

保存 Request URL 前桥接服务必须在线

保存 Event Subscriptions URL 会立即触发一次 url_verification POST。如果隧道上没有服务在监听,Slack 会报 "Your URL didn't respond" 且 URL 无法保存。因此先 bun run dev,再粘贴 URL。

Anthropic webhook 是按 workspace 隔离的

在 Console 注册的端点只会收到同一 workspace 内会话的事件。若 ANTHROPIC_API_KEY 属于 workspace A、但端点在 workspace B 注册,结果是"零投递、无报错"。注册 Webhooks 页面时的工作区选择器必须与 API key 所在 Workspace 列保持一致。

workspace 级 webhook 会收到该 workspace 的所有会话

如果 Anthropic workspace 与其他 Agent、脚本或同事共享,那么 workspace 内每一次 session.status_idled 都会打到你的端点。处理器的应对策略:

  • 永远先 retrieve 再过滤sessions.retrieve(id) → 检查自己的 slack_channel metadata key → 不存在就 204 退出。必须在 events.list() 或任何其他工作之前完成,否则无关会话会在处理器深处抛 404。
  • sessions.retrieve 捕获 404/403——同一 workspace 内由其他 API key 创建的会话,你的 key 读不到。
  • 生产环境使用专用 Anthropic workspace。每个无关会话都要消耗一次 retrieve() 调用来丢弃;只含本 Agent 会话的 workspace 则完全避免这种开销。

unwrap() 需要普通 header 映射

client.beta.webhooks.unwrap(body, {headers})(SDK ≥ 0.95.1)需要 Record<string, string>,而不是 fetch 的 Headers 对象。请传 Object.fromEntries(req.headers)

event.id 就是幂等键

Anthropic 会用相同的顶层 event.id 重试失败投递;Slack 则用 body 内相同的 event_id 重试。两者都要去重。一旦处理或忽略某事件就返回 2xx——返回任何其他状态都会触发重试,而 Anthropic 约连续 20 次失败会自动禁用你的端点。

静默失败的调试路径

skill.md 给出了一张非常实用的"对症下药"表,核心思路是先判断断点在哪条链路

  • 桥接日志里完全没有内容 → Slack 根本没到达你。用 curl localhost:4040/api/requests/http 查看 ngrok 的请求日志。看不到 /slack/events POST = Event Subscriptions 没保存,或 Socket Mode 被打开了。
  • [agent] kickoff 已打印但没有回复 → 看 ngrok 日志里有没有 /cma-webhook POST:没有,则是 Anthropic workspace 不匹配或端点未保存;出现 401,是 ANTHROPIC_WEBHOOK_SIGNING_KEY 不匹配;返回了 204 但 Slack 没发帖,则是 SLACK_BOT_TOKEN 错误(检查是否误填 xapp-)或缺 chat:write scope。
  • 400 Invalid agent ID.env.local 里还是 agent_... 占位符。从 bun run setup 的输出重新粘贴真实 ID。
  • chat.postMessagenot_in_channel → 先把 bot /invite 进频道。

生产化改造方向

skill.md 的 Production notes 与 CLAUDE.md 共同勾勒出从本地 demo 走向生产的四条路径:

  1. 替换 ngrok:用真正的部署(网关/公网入口)顶替隧道,业务代码无需任何改动。
  2. 把内存幂等集替换为共享存储:当前两个端点分别用内存 SetseenEventIds)做去重,多实例部署会失效,需要换成 Redis / 数据库实现跨实例幂等。
  3. 多 workspace(分布式)Slack 应用:把静态 SLACK_BOT_TOKEN 换成按 metadata.slack_team 键控的 per-team 令牌存储,并补充 Slack OAuth 授权流程。
  4. 多轮对话记忆:每次 @ 都会创建全新的 CMA 会话(跨轮无记忆)。要做线程内连续对话,应缓存 thread_ts → session_id 映射,后续消息改用 sessions.events.send 追加而非重复 sessions.create

此外,CLAUDE.md 还列举了在基础桥接之上可选的扩展方向,均可通过编辑 setup/create-agent.tssrc/agent.ts 实现:

  • 挂载 GitHub 仓库:在 sessions.createresources 中加入 {type: "github_repository", ...}
  • 接入 MCP 工具:给 Agent 配 mcp_servers + mcp_toolset,让 Agent 不仅能回复、还能"动手"(如 Slack/GitHub MCP),凭据走 vault,通过 vault_ids 挂到会话;
  • Outcomes 评估循环:用 user.define_outcome 事件替代 user.message,实现按 rubric 评分的迭代;
  • 多 Agent(Multiagent):在 Agent 上配置 multiagent: {type: "coordinator", agents: [...]} 形成协调者 + 子 Agent 编队;
  • 跨会话记忆:通过 resources: [{type: "memory_store", ...}] 持久化;
  • 自定义工具:通过 agent.custom_tool_use / user.custom_tool_result 在宿主侧执行。

小结

本案例展示了一个值得复用的集成范式:异步后台 Agent 与即时消息平台之间,靠"两个 webhook 信号 + 一次会话 metadata 存取"完成无状态桥接。核心不变量有三条——把路由状态写进 CMA 会话的 metadataslack_channelslack_thread_tsslack_team);对 workspace 级 webhook 一律"先 retrieve 再按 metadata 过滤";对 Slack 3 秒确认窗口采用签名校验 + 幂等去重 + fire-and-forget 的响应策略。按仓库内 README.mdskill.mdsrc 下源码的组合,你可以快速复刻、调试并扩展出自己的 Slack × CMA 助手。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388