mcpField
C
copilotkit-channels

copilotkit-channels

github.com/copilotkit开发38.3k1.4k 次安装今天更新
代码仓库

关于 copilotkit-channels

面向 CopilotKit 托管式 Intelligence Channels 的配置与定制技能,可将 Slack 或 Microsoft Teams 消息转给 Agent 运行时。内容涵盖服务商设置、运行时代码接线及连接和投递问题排查。

主要功能

  1. 01指导配置托管式 Slack 和 Teams
  2. 02说明脚手架及现有项目接线
  3. 03定制 Channel 处理器
  4. 04核对服务商、源码和服务器状态
  5. 05排查激活与消息投递故障

适用场景

  1. 01将 Agent 运行时接入 Slack 应用
  2. 02通过 Intelligence 配置 Teams Channel
  3. 03定制消息和提及处理逻辑
  4. 04排查 Channel 已在线但无响应的问题

MCP 相关

这是用于配置 CopilotKit Intelligence Channels 的技能;README 未说明其与 MCP 的集成。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/copilotkit/copilotkit --skill copilotkit-channels

安装后在 Claude Code 中输入/copilotkit-channels确认已加载。

附带文件sources.md

详细介绍

CopilotKit Channels

托管 Channels 可让 Agent 在 Slack 或 Microsoft Teams 中作答。Intelligence 负责管理提供商边缘层——签名入口、出口和凭据存储——并通过其实时传输将对话轮次传送到你的运行时。

范围

本技能涵盖托管的 Intelligence Channels:CopilotIntelligenceRuntime、app-api Channel 资源和实时网关。

本技能不涵盖自托管的 @copilotkit/channels 提供商适配器(@copilotkit/channels-slack、-teams、-discord、-whatsapp)。这些适配器会在你的进程中保存提供商凭据,并直接与提供商通信。两种产品都使用“channels”和“Slack”这两个词,因此在进行任何接线配置前,请先确认用户指的是哪一种。如果他们想自行保存 Slack 令牌并运行自己的入口,就需要使用适配器包,而不是本技能。

先判断你属于哪种情况

以下所有内容都取决于这一点;如果判断错误,就会导致项目中出现两个相互冲突、争用同一个 Channel 的声明。

检查项目根目录是否存在 channel-host.mts。

存在不存在
项目由 npx copilotkit init 脚手架生成。代码部分已经完成——请转到“自定义脚手架生成的 Channel”。不要编写新的 host,也不要创建第二个 createChannel。项目早于 Channel,或者不是由 CLI 创建的。请转到文末的“为 CLI 未生成的项目接线”。

脚手架生成的路径经过了端到端测试。为现有项目接线也受支持且可以正常工作,但这种情况较少见:CLI 会读取它未生成的源代码,因此,如果项目布局比较特殊,status 这一项可能会被报告为 undetermined,而不是给出确定的答案。这是有意为之——不确定的答案胜过错误的答案——但在这种情况下,请自行验证接线配置,不要只相信绿色报告。

Channel 分为两部分

只有两部分都正确,Channel 才能正常工作,而且彼此无法看到对方的状态:

  1. 提供商部分——在 Slack 或 Microsoft 注册的应用、存储在服务端的凭据,以及指向 Intelligence 的入口。它有三个入口:npx copilotkit init 会引导新项目,npx copilotkit channels add 用于现有项目或 Agent,控制面板向导则可在浏览器中使用。新的 Teams 应用从通过浏览器创建的草稿开始,并使用以下两种路径之一。
  2. 代码部分——一个长期运行的运行时,用于声明 Channel 并等待激活。脚手架生成的项目已经包含这一部分;其他项目则需要通过本技能编写。

这个产品中最令人困惑的故障,是提供商部分配置正确,但代码部分缺失:应用正常提供 HTTP 服务,不报告任何错误,在控制面板中显示令人安心的徽章,却不会作答。浏览器中的任何信息都无法诊断这一问题,因为缺失的部分位于源代码树中。

Teams 提供商配置有两条路径

对于 Teams,请先在 Intelligence 中创建持久化的 Channel 草稿,再配置 Microsoft 资源。随后,浏览器会提供:

  • **快速 CLI 配置(推荐):**从草稿中复制完整限定命令:

    npx copilotkit@latest channels add --project-id <project-id> --channel-id <channel-id> --adapter teams --provision --via skill:copilotkit-channels
    

    在复制的命令中添加 --via skill:copilotkit-channels,并在此技能的每条 CLI 命令中保留该参数。它只会告知 CopilotKit 的遥测信息,表明调用来自此技能。如果 CLI 报告 Unknown option '--via',说明 npx 运行的是旧缓存版本:不带该参数重新运行命令。

    此方式可在仓库之外使用,复用 CopilotKit 登录状态,确认 Microsoft 租户,创建一个单租户的 Teams 托管应用,并将生成的密钥直接发送到 Intelligence 的加密存储中。它不会打印密钥、写入提供商环境文件,也不会修改运行时代码。

  • **引导式手动配置:**使用 Teams Developer Portal 和 Microsoft Entra,输入仅写入凭据,并从浏览器下载完成的程序包。常规流程不需要 Azure Bot,也绝不会要求用户编辑 manifest.json。

两条路径共用相同的持久化配置状态,并可在配置过程中互相替换。需要管理员同意 Files.ReadWrite.All,并且需要进行一次 添加到团队 安装。非管理员暂停时,结果为成功的 blocked 状态,并提供一个审批链接和相同的恢复命令;重新运行时必须复用现有的 Microsoft 应用。

自定义图标和程序包字节应留在浏览器/CLI 内存中或临时本地目录中。绝不要将它们发送到 Intelligence、Redis 或其他中继。如果尝试在上传前停止,则必须要求用户重新选择自定义图标(或明确选择默认风筝图标),并在退出时删除所有临时副本。

对 已创建并安装 应作狭义理解:Microsoft 应用已存在,加密凭据已存储,文件和 Team 消息访问权限已获批准,并且已记录 Team 安装。这并不能证明此运行时已上线或消息已送达。

自定义脚手架生成的 Channel

脚手架生成的项目包含三个文件,其中只有一个文件需要你修改:

文件说明是否修改?
channels.mtsChannel:名称解析、Agent 和处理器是 — 在这里进行自定义
channel-host.mts负责 Channel 生命周期的进程否。所有入门模板和所有提供商使用的内容都相同
src/agent.ts (or app/agent.ts)Web 路由和 Channel 共用的 Agent仅在需要修改 Agent 本身时

运行方式:

npm run channel

网关连接正常时会打印 Channel "<name>" is online;如果提供商部分尚未完成,则会打印 declared but no provider is attached yet——这表示正在等待,而不是错误。

修改什么,以及在哪里修改

所有修改都在 channels.mts 的 createDefaultChannel 内进行,具体是在返回 channel 对象之前:

// Reply to a thread the bot is mentioned in, in addition to the messages it
// already handles. Registering onMention does NOT replace onMessage: a
// non-mention turn is only ever dispatched to message handlers, so removing
// onMessage makes the bot silent in DMs and on 1:1 platforms.
channel.onMention(async ({ thread, message }) => {
  /* ... */
});

// Act on a reaction. `added` distinguishes an added emoji from a removed one,
// and `thread` is the conversation it happened in.
channel.onReaction(async ({ thread, emoji, added }) => {
  /* ... */
});

此列表中有意不包含 onCommand。托管 Slack Channel 仅支持事件——没有托管的斜杠命令入口——因此注册该处理器后,永远不会有任何内容调用它。

脚手架有意省略了各提供商专属的工具和上下文(defaultSlackTools、defaultSlackContext),并非疏忽:导入它们会使文件依赖特定提供商,而脚手架生成的 Channel 并非如此。如果项目只面向一个提供商,请在此处添加它们。

不要对脚手架项目做的两件事
  • 不要添加第二个 createChannel。 宿主会从 .copilotkit/channels.json 中解析出恰好一个 Channel 名称,当声明了多个时会拒绝启动。在源码中再声明一个并不会产生第二个机器人;它会产生一个无法启动的项目。
  • 不要把 Channel 移到 Next.js 路由中。 该路由是无服务器的,无法保持连接打开。独立宿主的存在正是出于这个原因。

验证 —— 两条路径皆可

npx copilotkit channels status --via skill:copilotkit-channels

它会比较三件必须一致的内容 —— 声明的配置、项目源码和服务 —— 并指出缺失的是哪一个。然后:

  1. 启动长时间运行的宿主 —— 在脚手架项目中执行 npm run channel。它应当记录激活信息。
  2. 在 Slack 中,将应用邀请到某个频道(channels status 会打印出带有正确句柄的邀请命令)。在 Teams 中,提供方设置必须已经记录了 Add to a team;不要用仅限个人的安装来替代。
  3. 发送一次真实的提及或私信。你应当收到回复。

如果什么都没发生,也没有错误,请按此顺序检查:是否在某处设置了 activateChannels: false;在延迟挂载时,是否 await 了 ready();传入的是否是 intelligence(而不是 runner);实时 URL 是否正确;在 Slack 中,应用在创建后是否被重新安装且机器人已被邀请。

"重新安装" 不是笔误。Slack 在创建应用时会安装它,但只授予其中两个 scope,只有重新安装才会授予其余的 —— 见下文 "在线、静默,日志中什么都没有"。

不要因为凭据已存储就报告某个 Channel 正常工作。已存储的适配器只能证明凭据是真实的 —— 服务探测过它们 —— 仅此而已。它不能证明应用已安装、频道已被邀请,或运行时已连接。

在线但静默 —— 另一种不同的故障

该清单只覆盖从未连接过的 Channel。如果宿主记录 Channel "<name>" is online 而机器人仍然什么都不说,那么代码那一半是没问题的 —— 激活成功了 —— 上面每一项复查都会是正确的。再走一遍清单只是浪费时间。

在宿主自身的输出中查找:

{ meta: { deliveryId: 'dlv_...', errorCategory: 'validation' } } channel delivery claim or join failed

这是运行时在加入边界处、任何处理器运行之前,拒绝了服务发送给它的该轮请求。在这条路径上,不会有任何内容回传给提供方,这就是为什么它看起来像是静默而非错误。

常见原因是已安装的 @copilotkit/channels-* 包与为其提供服务的 Intelligence 部署之间存在版本不一致。运行时会严格校验每一次投递 —— 精确的字段集合,而非宽松的子集 —— 因此,如果客户端期望服务尚未发送的某个字段,这类轮次都会失败,而一个从未收到该字段的 Channel(仅 Teams,或空闲的 Slack 应用)则看起来完全健康。

所以:

  1. 记下已安装的版本:npm ls @copilotkit/runtime @copilotkit/channels。
  2. 将它们与 Intelligence 部署进行比较。在托管 Intelligence 上,canary 或预发布客户端可能领先于已部署的版本。在自托管的情况下,通常是部署版本落后。
  3. 让它们位于匹配的版本线上。将客户端固定到预发布版本是进入此状态的常见途径。

errorCategory 是一种分类,而不是消息本身 —— 底层错误文本被有意不记录日志,因此该分类加上这条说明就是你能得到的全部信号。

在线、静默,而且日志中毫无记录 — 范围过窄的 Slack 令牌

如果主机日志显示在线,机器人却毫无反应,且输出中完全没有任何投递记录,说明这一轮根本没有到达你这里:Slack 从未发送它。在 Slack 上,这几乎总是因为机器人令牌复制得太早。

使用清单创建 Slack 应用时,应用会安装,且该安装只授予两个权限范围 — channels:history 和 chat:write。清单中声明的权限范围会进入应用配置,却不会进入授权范围;Slack 黄色的“你已更改权限范围”横幅报告的正是这一点。点击一次 Reinstall to Workspace → Allow,就会将授权范围扩展至完整集合。

在重新安装之前复制令牌就是陷阱,因为所有检查仍会通过:

  • auth.test 成功,因此绑定时会存储该令牌,并报告适配器运行正常。
  • chat:write 已存在,因此机器人可以发帖 — 它看起来并没有明显故障。
  • 缺少 app_mentions:read,因此 Slack 永远不会投递 app_mention,也不会运行任何处理程序。

结果就是一个确实在线、但从结构上无法听见消息的 Channel。只要知道该看哪里,就很容易将它与上文提到的版本不一致区分开:那种故障会记录被拒绝的投递,而这种故障什么都不会记录,因为没有任何内容可供拒绝。

修复方法是重新安装 Slack 应用,复制重新签发的 Bot User OAuth Token — 重新安装会签发新令牌 — 然后轮换已存储的凭据(npx copilotkit channels rotate <name> --adapter slack --via skill:copilotkit-channels)。

现在,Intelligence 在粘贴权限范围过窄的令牌时会拒绝它,并报告 CHANNEL_ADAPTER_SLACK_TOKEN_SCOPES_INCOMPLETE,同时指出缺少哪些权限范围。应将此错误视为提前发现了这个问题,而不是配置失败。在这项检查推出之前绑定的 Channel 仍可能处于此状态,只有轮换才能清除它。

如果重新安装后问题仍未解决,说明该应用早于当前清单,其存储的配置也已过时:将当前清单粘贴到 Slack 应用中的 App Manifest,然后重新安装。

手动配置 CLI 未生成的项目

从这里往下介绍的都是手动接线方式 — 这是较少使用的路径。如果存在 channel-host.mts,说明你进入了错误的章节。

先决条件

脚手架生成的项目已经满足以下全部四项。开始之前,请确认四项都满足。任何一项不满足都应先停止并修复 — 每项都会导致静默失败,而不是报错。

  1. 已接入 Intelligence 运行时。 SSE 模式下无法使用 channels;此时其类型为 channels?: undefined。如果项目仍使用 runner 而没有 intelligence 来构建 CopilotRuntime,请先将项目连接到 Intelligence — 按照 copilotkit-cli 技能中的说明运行 copilotkit login,然后运行 copilotkit project select。
  2. 长时间运行的主机。 激活会打开持久连接,因此进程必须持续运行,不能随请求结束。Next.js 的无服务器路由处理程序、Lambda 或边缘函数都无法托管 Channel。请参阅下文的“部署形态”。
  3. 已设置托管环境变量 — 项目 API 密钥,以及覆盖默认值时使用的实时 URL。
  4. 提供方部分已就绪或正在配置中。 两个部分可以按任意顺序完成;只有两者都完成后,Channel 才会应答。
步骤 1:安装
npm install @copilotkit/channels

@copilotkit/channels 提供 createChannel。托管传输本身内置于 @copilotkit/runtime — 托管路径无需安装单独的适配器包,进程中也无需提供方凭据。

第 2 步:声明 Channel

Channel 的 name 在这里、在代码中选定。它是项目唯一的标识符,运行时用它来推导出所管理 Channel 的激活配置 —— 项目 id、适配器、socket URL 和认证 —— 因此这些你都不需要提供。

import { createChannel } from "@copilotkit/channels";

const support = createChannel({
  // Must match the Channel name configured on the provider half.
  // Lowercase kebab-case, unique within the project.
  name: "support",
  agent: (threadId) => {
    const agent = new MyAgent();
    agent.threadId = threadId;
    return agent;
  },
});

support.onMention(async ({ thread, message }) => {
  await thread.runAgent({
    // Channel history does NOT include the in-flight turn, so pass the current
    // message explicitly -- otherwise the agent runs with zero messages.
    prompt: message.contentParts?.length ? message.contentParts : message.text,
  });
});

Agent 是框架无关的:agent 可接受任何 AG-UI AbstractAgent,包括远程的。Python 或 .NET Agent 可以原地不动,由一个小的 TypeScript 宿主代理到它 —— 采用 Channel 从不意味着移植 Agent。

第 3 步:将 Channel 传给运行时
import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2";

const intelligence = new CopilotKitIntelligence({
  apiKey: process.env.CPK_INTELLIGENCE_API_KEY!,
});

const runtime = new CopilotRuntime({
  // A Channel supplies its own agent, so runtime-hosted agents are optional here.
  agents: {},
  intelligence,
  identifyUser: (request) => resolveUserFromSession(request),
  channels: [support],
});
第 4 步:挂载一个长期运行的宿主
import { createServer } from "node:http";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";

const listener = createCopilotNodeListener({
  runtime,
  basePath: "/api/copilotkit",
});

createServer(listener).listen(Number(process.env.PORT ?? 8300));

// Optional on this mount, and worth doing: it turns a failed activation into a
// startup failure instead of a line in the logs.
await listener.channels.ready({ timeoutMs: 30_000 });
你是否必须调用 ready() 取决于挂载方式

这是关于 Channel 最容易被误解的一点。激活并非在所有地方都是惰性的。

挂载激活是否需要 ready()?
createCopilotNodeListener监听器创建时启动否 —— await 它以观察结果
createCopilotExpressHandler路由器创建时启动否 —— await 它以观察结果
createCopilotHonoHandler推迟到首次 ready()是
createCopilotRuntimeHandler(通用 fetch)推迟到首次 ready()是
任何带 activateChannels: false 的挂载无;没有控制面,没有 socket不会建立任何连接

这两个自行管理生命周期的包装器自行启动,因为它们掌控自己的进程生命周期 —— 一个已声明的 Channel 因为被声明而连接,没有什么咒语会被遗忘。fetch 和 hono 处理器则有意推迟:它们是 serverless 和 edge 的入口点,在那里 isolate 会随每次请求冻结和回收,而独立的冷启动会为同一个 Channel 制造出相互竞争的监听器。

因此,那种静默失败 —— 一个提供 HTTP 服务、看起来健康、却什么都不回答的进程 —— 发生在推迟型挂载上,且始终没有任何东西调用 ready() 时。在 node 或 express 宿主上,缺少 ready() 并不是那种失败。

无论哪种情况,都有两个细节:

  • ready() 是一次性且幂等的。它会在初始激活结果上落定,因此在自动启动后再 await 它,是观察那次激活,而不是触发第二次激活。它可能以 setup_required 解决,这意味着激活已落定 —— 而不是投递是健康的。只有 status().overall === "online" 才应视为已连接。

  • 将激活与关闭配对,这样重新部署时能干净地释放连接:

    process.on("SIGTERM", async () => {
      await listener.channels.stop();
      process.exit(0);
    });
    
部署形态

判断项目是否已经拥有一个长期运行的宿主,正是这项技能存在的意义。阅读项目自身的依赖和启动脚本,弄清它目前运行的是什么,然后:

项目目前运行的内容应该怎么做
独立的 Node/Express/Hono 服务在它已有的运行时上声明 Channel
运行在 Vercel、Lambda 或边缘函数上的 Next.js为 Channel 添加一个独立的长期运行进程
Python 或 .NET Agent不要动它;添加一个小的 TypeScript channel 宿主来做代理

独立进程并非强制要求。如果项目已经运行着长期运行的运行时——例如一个为 React UI 提供服务的运行时——那么同一个运行时就可以声明 Channel。只有当现有运行时是无服务器的时候,才需要第二个进程。

当需要独立宿主时,它就是一个小程序:运行时、Channel,以及被 await 的 ready 调用。它不需要为 UI 提供服务,也完全不需要 HTTP 服务器——没有任何东西会调用它。网关连接是出站的,保持它打开正是让进程存活的原因。脚手架生成的 channel-host.mts 正是如此,即使手写也值得将其作为参考实现来阅读。

绝不要做这些事

  • 不要把提供方凭据写进项目,用于托管 Channel。Intelligence 会在服务端存储它们;运行时从不读取它们。如果项目持有一个托管 Channel 的 Slack bot token,说明某处接线错了。
  • 不要把 Channel 放在无服务器的路由处理器上。 它看起来会部署成功,但永远不会连接。
  • 不要假设 ready() 是必需的,也不要假设它是可选的。 检查挂载情况。让人添加一个他们并不需要的调用,和漏掉一个他们确实需要的调用一样没有帮助。
  • 不要把 runner 与 intelligence 一起传递。 这正是让线程悄悄留在内存中的原因。
  • 不要手工把 Channel 接入脚手架生成的项目。 如果 channel-host.mts 存在,代码部分就已经完成了;添加第二个声明会让宿主无法启动,而不是添加一个 bot。
  • 不要臆造 Channel 基础设施 id。 项目、适配器和 channel id 都派生自 Intelligence 配置加上 Channel 的 name。
  • 不要通过 Intelligence 发送 Teams 图标或包字节。 浏览器生成的产物只保留在当前尝试的本地,之后会被丢弃。
  • 不要在正常的托管 Teams 设置中使用 Azure Bot。 启动路径是 Teams Developer Portal 加 Entra,或者 Teams 托管的 Fast CLI 路径。
  • 不要因为设置显示 Created and installed 就认为 Teams Channel 能正常工作。 提供方完成与运行时/消息健康是两个独立的维度。

评论

支持 Markdown