copilotkit-channels
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCopilotKit Channels
CopilotKit Channels
Managed Channels let an agent answer in Slack or Microsoft Teams. Intelligence owns the provider edge — signed ingress, egress, credential storage — and delivers turns to your runtime over its realtime transport.
托管渠道允许Agent在Slack或Microsoft Teams中回复。智能层负责提供商端——签名入站、出站、凭证存储——并通过实时传输将对话轮次传递到你的运行时。
Scope
适用范围
This skill covers managed Intelligence Channels: , app-api Channel resources, and the realtime gateway.
CopilotIntelligenceRuntimeIt does not cover the self-hosted provider adapters (, , , , ). Those hold provider credentials in your process and talk to the provider directly. Both products use the words "channels" and "Slack", so confirm which one the user means before wiring anything. If they want to hold their own Slack tokens and run their own ingress, they want the adapter packages, not this skill.
@copilotkit/channels@copilotkit/channels-slack-teams-discord-telegram-whatsapp本技能涵盖托管智能渠道:、应用API渠道资源以及实时网关。
CopilotIntelligenceRuntime本技能不涵盖自托管的提供商适配器(、、、、)。这些适配器会在你的进程中存储提供商凭证,并直接与提供商通信。两款产品都使用“channels”和“Slack”字样,因此在进行任何配置前,请确认用户所指的是哪一款。如果用户希望自行持有Slack令牌并运行自己的入站服务,他们需要的是适配器包,而非本技能。
@copilotkit/channels@copilotkit/channels-slack-teams-discord-telegram-whatsappDecide which path you are on first
先确定你的路径
Everything below depends on this, and getting it wrong produces a project with two Channel declarations that fight over one Channel.
Look for at the project root.
channel-host.mts| It is there | It is not |
|---|---|
The project was scaffolded by | The project predates the Channel, or was not made by the CLI. Go to "Wiring a project the CLI did not generate", at the end. |
The scaffolded path is the one exercised end to end. Wiring an existing project is supported and works, but it is less trodden: the CLI reads source it did not generate, so a project laid out unusually may get a leg reported as rather than a confident answer. That is deliberate — an unsure answer beats a wrong one — but verify the wiring yourself there rather than trusting a green report.
statusundetermined以下所有内容都取决于这一点,如果判断错误,会导致项目出现两个渠道声明,从而引发冲突。
检查项目根目录下是否存在文件。
channel-host.mts| 存在该文件 | 不存在该文件 |
|---|---|
项目是通过 | 项目早于渠道功能推出,或并非由CLI生成。请前往文末的“对接非CLI生成的项目”。 |
脚手架路径是经过端到端验证的路径。对接现有项目是受支持且可行的,但使用较少:CLI会读取非自身生成的源代码,因此结构异常的项目可能会显示状态为,而非明确结果。这是有意设计的——不确定的结果总比错误结果好——但在此路径下,请自行验证配置,不要完全依赖绿色状态报告。
statusundeterminedA Channel has two halves
渠道包含两部分
A Channel only works when both are correct, and each half is invisible from the other:
- The provider half — an app registered with Slack or Microsoft, credentials stored server-side, ingress pointed at Intelligence. Three entry points converge on it: guides a new project,
npx copilotkit inithandles an existing project or agent, and the dashboard wizard works in a browser. A new Teams app begins with the browser-created draft and uses one of the two paths below.npx copilotkit channels add - The code half — a long-running runtime that declares the Channel and awaits activation. A scaffolded project already has this; for anything else, it is what this skill writes.
The most confusing failure in this product is a correct provider half with a missing code half: the app serves HTTP normally, reports no error, shows an encouraging badge in the dashboard, and answers nothing. Nothing in the browser can diagnose it, because the missing piece is in the source tree.
渠道只有在两部分都配置正确时才能正常工作,且每一部分对另一部分都是不可见的:
- 提供商部分——在Slack或Microsoft注册的应用,凭证存储在服务器端,入站指向智能层。有三个入口可完成此部分:引导新项目,
npx copilotkit init处理现有项目或Agent,仪表板向导在浏览器中操作。新的Teams应用从浏览器创建的草稿开始,并使用以下两种路径之一。npx copilotkit channels add - 代码部分——一个长期运行的运行时,用于声明渠道并等待激活。脚手架生成的项目已包含此部分;对于其他项目,这正是本技能要编写的内容。
该产品最令人困惑的故障是提供商部分配置正确,但代码部分缺失:应用正常提供HTTP服务,无错误报告,仪表板显示令人鼓舞的标识,但完全不回复消息。浏览器无法诊断此问题,因为缺失的部分在源代码中。
Teams provider setup has two peer paths
Teams提供商设置有两种并行路径
For Teams, create the durable Channel draft in Intelligence before provisioning Microsoft resources. The browser then offers:
-
Fast CLI setup (recommended): copy the fully scoped command from the draft:bash
npx copilotkit@latest channels add --project-id <project-id> --channel-id <channel-id> --adapter teams --provisionIt works outside a repository, reuses the CopilotKit login, confirms the Microsoft tenant, creates one single-tenant Teams-managed app, and sends the generated secret directly to encrypted Intelligence storage. It does not print the secret, write provider environment files, or modify runtime code. -
Guided manual setup: use Teams Developer Portal and Microsoft Entra, enter write-only credentials, and download the finished package from the browser. The normal path does not need Azure Bot and never asks the user to edit.
manifest.json
Both paths use the same durable setup state and can replace one another mid-setup. administrator consent and one Add to a team installation are required. A non-admin pause is a successful outcome with one approval link and the same resume command; rerunning it must reuse the existing Microsoft app.
Files.ReadWrite.AllblockedCustom icon and package bytes stay in browser/CLI memory or a temporary local directory. Never send them to Intelligence, Redis, or another relay. If an attempt stops before upload, require the user to select custom icons again (or explicitly choose the default kite) and delete every temporary copy on exit.
Treat Created and installed narrowly: the Microsoft app exists, encrypted credentials are stored, file and Team message access are approved, and a Team installation was recorded. It is not proof that this runtime is online or that a message was delivered.
对于Teams,请先在智能层创建持久化渠道草稿,再配置Microsoft资源。浏览器会提供:
-
快速CLI设置(推荐):从草稿中复制完整的命令:bash
npx copilotkit@latest channels add --project-id <project-id> --channel-id <channel-id> --adapter teams --provision该命令可在仓库外运行,复用CopilotKit登录信息,确认Microsoft租户,创建一个单租户Teams托管应用,并将生成的密钥直接发送到加密的智能层存储。它不会打印密钥、写入提供商环境文件或修改运行时代码。 -
引导式手动设置:使用Teams开发者门户和Microsoft Entra,输入只写凭证,并从浏览器下载完成的包。常规路径不需要Azure Bot,也不会要求用户编辑。
manifest.json
两种路径使用相同的持久化设置状态,可在设置中途互相替代。需要管理员权限,以及一次“添加到团队”的安装操作。非管理员用户的暂停会产生一个成功的结果,包含一个审批链接和相同的恢复命令;重新运行命令必须复用现有的Microsoft应用。
Files.ReadWrite.Allblocked自定义图标和包字节会保留在浏览器/CLI内存或临时本地目录中。切勿将它们发送到智能层、Redis或其他中继服务。如果尝试在上传前停止,需要求用户重新选择自定义图标(或明确选择默认风筝图标),并在退出时删除所有临时副本。
严格定义“已创建并安装”:Microsoft应用已存在,加密凭证已存储,文件和团队消息访问权限已获批,且已记录团队安装操作。这并不证明运行时已上线或消息已送达。
Customising a scaffolded Channel
自定义脚手架生成的渠道
A scaffolded project ships three files, and only one of them is yours to change:
| File | What it is | Change it? |
|---|---|---|
| The Channel: its name resolution, its agent, and its handlers | Yes — this is where customisation goes |
| The process that owns the Channel's lifetime | No. It is identical in every starter and for every provider |
| The agent both the web route and the Channel serve | Only to change the agent itself |
Run it with:
bash
npm run channelIt prints when the gateway connection is live, or when the provider half is unfinished — which is a waiting state, not an error.
Channel "<name>" is onlinedeclared but no provider is attached yet脚手架生成的项目包含三个文件,其中只有一个文件可由你修改:
| 文件 | 说明 | 是否可修改? |
|---|---|---|
| 渠道文件:包含名称解析、Agent以及处理程序 | 是——自定义操作都在此处进行 |
| 管理渠道生命周期的进程 | 否。每个启动项目和每个提供商的该文件都是相同的 |
| Web路由和渠道共用的Agent | 仅在修改Agent本身时更改 |
运行命令:
bash
npm run channel当网关连接生效时,会打印;当提供商部分未完成时,会打印——这是等待状态,而非错误。
Channel "<name>" is onlinedeclared but no provider is attached yetWhat to change, and where
修改内容及位置
Everything happens inside in , on the object before it is returned:
createDefaultChannelchannels.mtschanneltypescript
// 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 }) => {
/* ... */
});onCommandPer-provider tools and context (, ) are a deliberate omission from the scaffold, not an oversight: importing them makes the file provider-specific, and the scaffolded Channel is not. Add them here when the project only ever targets one provider.
defaultSlackToolsdefaultSlackContext所有操作都在的函数内,在返回对象之前进行:
channels.mtscreateDefaultChannelchanneltypescript
// 除了已处理的消息外,还回复Bot被提及的线程。注册onMention不会替换onMessage:非提及的对话轮次只会分派给消息处理程序,因此移除onMessage会使Bot在私信和一对一平台中静默。
channel.onMention(async ({ thread, message }) => {
/* ... */
});
// 对表情反应做出操作。`added`用于区分添加和移除表情,`thread`是发生反应的对话。
channel.onReaction(async ({ thread, emoji, added }) => {
/* ... */
});onCommand脚手架中故意省略了提供商特定的工具和上下文(、),并非疏忽:导入这些内容会使文件依赖于特定提供商,而脚手架生成的渠道是通用的。当项目仅针对一个提供商时,可在此处添加这些内容。
defaultSlackToolsdefaultSlackContextTwo things not to do to a scaffolded project
脚手架项目的两大禁忌
- Do not add a second . The host resolves exactly one Channel name from
createChanneland refuses to start when several are declared. A second declaration in source does not produce a second bot; it produces a project that will not boot..copilotkit/channels.json - Do not move the Channel into the Next.js route. That route is serverless and cannot hold a connection open. The separate host exists for that reason.
- 不要添加第二个。 主机会从
createChannel中解析出恰好一个渠道名称,当声明多个渠道时会拒绝启动。源代码中的第二个声明不会生成第二个Bot;反而会导致项目无法启动。.copilotkit/channels.json - 不要将渠道移动到Next.js路由中。 该路由是无服务器的,无法保持连接打开状态。单独的主机正是为此而存在。
Verify — either path
验证——无论哪种路径
bash
npx copilotkit channels statusThat compares three things that must agree — the declared configuration, the project source, and the server — and names whichever is missing. Then:
- Start the long-running host — in a scaffolded project. It should log the activation.
npm run channel - In Slack, invite the app to a channel (prints the invite command with the right handle). In Teams, provider setup must already record Add to a team; do not substitute a personal-only install.
channels status - Send a real mention or direct message. You should get a reply.
If nothing happens and there is no error, check in this order: is set anywhere; on a deferring mount, is awaited; is passed (not a ); is the realtime URL correct; on Slack, was the app reinstalled after creation and the bot invited.
activateChannels: falseready()intelligencerunner"Reinstalled" is not a typo. Slack installs the app when it creates it, with two of its scopes, and only a reinstall grants the rest — see "Online, silent, and nothing in the log at all" below.
Do not report a Channel as working because credentials were stored. A stored adapter proves the credentials are real — the server probed them — and nothing more. It does not prove the app is installed, that a channel was invited, or that a runtime is connected.
bash
npx copilotkit channels status该命令会对比三个必须一致的内容——声明的配置、项目源代码和服务器——并指出缺失的部分。然后:
- 启动长期运行的主机——脚手架项目中运行。它应记录激活信息。
npm run channel - 在Slack中,邀请应用加入渠道(会打印带有正确句柄的邀请命令)。在Teams中,提供商设置必须已记录“添加到团队”;不要用仅个人安装替代。
channels status - 发送真实的提及消息或私信。你应收到回复。
如果没有任何反应且无错误,请按以下顺序检查:是否在任何地方设置了;在延迟挂载时,是否等待了;是否传入了(而非);实时URL是否正确;在Slack中,应用创建后是否重新安装并邀请了Bot。
activateChannels: falseready()intelligencerunner“重新安装”并非笔误。Slack在创建应用时会安装它,但仅授予两个权限范围,只有重新安装才能授予其余权限——请参阅下文的“已上线、静默且日志中无任何内容”。
不要因为凭证已存储就认为渠道正常工作。存储的适配器仅证明凭证有效——服务器已验证——仅此而已。这并不证明应用已安装、渠道已邀请或运行时已连接。
Online, but silent — a different failure
已上线但静默——另一种故障
That checklist only covers a Channel that never connected. If the host logs and the bot still says nothing, the code half is fine — activation succeeded — and every item above will come back correct. Walking the list again is wasted time.
Channel "<name>" is onlineLook in the host's own output for:
{ meta: { deliveryId: 'dlv_...', errorCategory: 'validation' } } channel delivery claim or join failedThat is the runtime rejecting the turn the server sent it, at the join boundary, before any handler runs. Nothing is posted back to the provider on this path, which is why it looks like silence rather than an error.
The usual cause is a version disagreement between the installed packages and the Intelligence deployment serving them. The runtime validates each delivery strictly — exact field sets, not a loose subset — so a client expecting a field its server does not yet send fails every turn of that kind, while a Channel that never receives one (Teams-only, or an idle Slack app) looks perfectly healthy.
@copilotkit/channels-*So:
- Note the installed versions: .
npm ls @copilotkit/runtime @copilotkit/channels - Compare them with the Intelligence deployment. On hosted Intelligence, a canary or prerelease client can run ahead of what is deployed. On self-hosted, the deployment is usually the one that lags.
- Move them onto matching lines. Pinning the client to a prerelease is the common way into this state.
errorCategory上述检查清单仅涵盖从未连接的渠道。如果主机记录但Bot仍无响应,则代码部分正常——激活成功——上述所有检查项都会显示正确。再次检查清单是浪费时间。
Channel "<name>" is online查看主机自身的输出:
{ meta: { deliveryId: 'dlv_...', errorCategory: 'validation' } } channel delivery claim or join failed这表示运行时在连接边界处拒绝了服务器发送的对话轮次,此时处理程序尚未运行。此路径不会向提供商返回任何内容,因此看起来像是静默而非错误。
通常的原因是已安装的包与提供服务的智能层部署版本不一致。运行时会严格验证每个交付内容——精确的字段集,而非松散的子集——因此客户端期望的字段服务器尚未发送时,此类对话轮次都会失败,而从未接收此类轮次的渠道(仅Teams,或闲置的Slack应用)看起来完全正常。
@copilotkit/channels-*因此:
- 记录已安装的版本:。
npm ls @copilotkit/runtime @copilotkit/channels - 将其与智能层部署版本对比。在托管智能层中,金丝雀版或预发布版客户端可能会领先于已部署的版本。在自托管环境中,部署版本通常会滞后。
- 将它们调整为匹配的版本。将客户端固定到预发布版是进入此状态的常见原因。
errorCategoryOnline, silent, and nothing in the log at all — a short-scoped Slack token
已上线、静默且日志中无任何内容——权限范围不足的Slack令牌
If the host logs online, the bot says nothing, and there is no delivery line of any kind in its output, the turn never reached you: Slack never sent it. On Slack that is almost always a bot token that was copied too early.
Creating a Slack app from a manifest installs it, and that install grants only two scopes — and . The manifest's declared scopes reach the app's configuration but not the grant, which is what Slack's yellow "you've changed the permission scopes" banner is reporting. One Reinstall to Workspace → Allow raises the grant to the full set.
channels:historychat:writeA token copied before that reinstall is the trap, because every check still passes:
- succeeds, so attaching stores it and reports the adapter healthy.
auth.test - is present, so the bot can post — it is not obviously broken.
chat:write - is absent, so Slack never delivers
app_mentions:read, and no handler ever runs.app_mention
The result is a Channel that is genuinely online and structurally deaf. Distinguishing it from the version disagreement above is easy once you know to look: that failure logs a rejected delivery, this one logs nothing, because there is nothing to reject.
Fix it by reinstalling the Slack app, copying the reissued Bot User OAuth Token — reinstalling issues a new one — and rotating the stored credential ().
npx copilotkit channels rotate <name> --adapter slackIntelligence now refuses a short-scoped token when it is pasted, with , and names the missing scopes. Treat that error as this problem caught early rather than as a setup failure. A Channel attached before that check existed can still be sitting in this state, and only a rotation clears it.
CHANNEL_ADAPTER_SLACK_TOKEN_SCOPES_INCOMPLETEIf a reinstall does not fix it, the app predates the current manifest and its stored configuration is short too: paste the current manifest into App Manifest in the Slack app, then reinstall.
如果主机记录已上线,Bot无响应,且输出中完全没有任何交付记录,则对话轮次从未到达你这里:Slack从未发送它。在Slack中,这几乎总是因为过早复制了Bot令牌。
从清单创建Slack应用时会自动安装,但此次安装仅授予两个权限范围——和。清单中声明的权限范围会显示在应用配置中,但不会被授予,这正是Slack黄色提示“你已更改权限范围”所报告的内容。一次“重新安装到工作区→允许”操作会将权限范围提升至完整集合。
channels:historychat:write过早复制的令牌是陷阱,因为所有检查仍会通过:
- 成功,因此附加存储会记录它并报告适配器正常。
auth.test - 已存在,因此Bot可以发送消息——它并非明显损坏。
chat:write - 缺失,因此Slack永远不会传递
app_mentions:read事件,处理程序永远不会运行。app_mention
结果是渠道确实已上线,但在结构上无法接收消息。一旦知道要查找什么,就很容易将其与上述版本不一致的故障区分开来:版本不一致会记录被拒绝的交付,而此故障无任何日志,因为没有可拒绝的内容。
修复方法是重新安装Slack应用,复制重新颁发的Bot用户OAuth令牌——重新安装会生成新令牌——并轮换存储的凭证()。
npx copilotkit channels rotate <name> --adapter slack智能层现在会在粘贴权限范围不足的令牌时拒绝,并提示,同时列出缺失的权限范围。将此错误视为提前发现的问题,而非设置失败。在此检查存在之前附加的渠道仍可能处于此状态,只有轮换凭证才能解决。
CHANNEL_ADAPTER_SLACK_TOKEN_SCOPES_INCOMPLETE如果重新安装无法解决问题,则应用早于当前清单,其存储的配置权限范围也不足:将当前清单粘贴到Slack应用的“应用清单”中,然后重新安装。
Wiring a project the CLI did not generate
对接非CLI生成的项目
Everything from here down is the hand-wiring path — the less-trodden one. If exists, you are in the wrong section.
channel-host.mts以下内容是手动对接路径——使用较少的路径。如果存在,则你进入了错误的章节。
channel-host.mtsPrerequisites
先决条件
A scaffolded project satisfies all four of these already. Before starting, confirm all four. Stop and fix any that fail — each one produces a silent failure rather than an error.
- The Intelligence runtime is wired. is not available in SSE mode; the type is
channelsthere. If the project still constructschannels?: undefinedwith aCopilotRuntimeand norunner, do the managed Intelligence step in the copilotkit-setup skill first.intelligence - A long-running host. Activation opens a persistent connection, so the process has to outlive a request. A Next.js route handler on serverless, a Lambda, or an edge function cannot host a Channel. See "Deployment shape" below.
- The hosted environment values are set — the project API key, and the realtime URL if you are overriding defaults.
- The provider half exists, or is in progress. The two halves can be done in either order; a Channel simply does not answer until both are done.
脚手架生成的项目已满足以下所有四个条件。开始之前,请确认所有条件都已满足。如果有任何条件不满足,请停止并修复——每个不满足的条件都会导致静默故障,而非错误。
- 智能层运行时已对接。 SSE模式下不支持;该模式下类型为
channels。如果项目仍使用channels?: undefined而非runner构造intelligence,请先完成copilotkit-setup技能中的托管智能层步骤。CopilotRuntime - 长期运行的主机。 激活会打开持久连接,因此进程必须比请求存活更久。无服务器的Next.js路由处理程序、Lambda或边缘函数无法托管渠道。请参阅下文的“部署形态”。
- 已设置托管环境变量——项目API密钥,以及(如果覆盖默认值)实时URL。
- 提供商部分已存在或正在进行中。 两部分可按任意顺序完成;只有当两部分都完成后,渠道才会回复消息。
Step 1: Install
步骤1:安装
bash
npm install @copilotkit/channels@copilotkit/channelscreateChannel@copilotkit/runtimebash
npm install @copilotkit/channels@copilotkit/channelscreateChannel@copilotkit/runtimeStep 2: Declare the Channel
步骤2:声明渠道
The Channel's is chosen here, in code. It is the project-unique identifier the runtime uses to derive the managed Channel's activation config — project id, adapter, socket URL and auth — so you supply none of those.
nametypescript
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,
});
});The agent is framework-agnostic: accepts any AG-UI , including a remote one. A Python or .NET agent stays exactly where it is and a small TypeScript host proxies to it — adopting Channels never means porting an agent.
agentAbstractAgent渠道的在此处代码中指定。它是运行时用于派生托管渠道激活配置——项目ID、适配器、套接字URL和身份验证——的项目唯一标识符,因此你无需提供这些内容。
nametypescript
import { createChannel } from "@copilotkit/channels";
const support = createChannel({
// 必须与提供商部分配置的渠道名称匹配。
// 小写短横线命名法,项目内唯一。
name: "support",
agent: (threadId) => {
const agent = new MyAgent();
agent.threadId = threadId;
return agent;
},
});
support.onMention(async ({ thread, message }) => {
await thread.runAgent({
// 渠道历史不包含进行中的对话轮次,因此请显式传递当前消息——否则Agent运行时无任何消息。
prompt: message.contentParts?.length ? message.contentParts : message.text,
});
});Agent是框架无关的:接受任何AG-UI ,包括远程Agent。Python或.NET Agent可保持在原有位置,由一个小型TypeScript主机代理——采用渠道并不意味着必须移植Agent。
agentAbstractAgentStep 3: Pass the Channel to the runtime
步骤3:将渠道传递给运行时
typescript
import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2";
const intelligence = new CopilotKitIntelligence({
apiKey: process.env.COPILOTKIT_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],
});typescript
import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2";
const intelligence = new CopilotKitIntelligence({
apiKey: process.env.COPILOTKIT_API_KEY!,
});
const runtime = new CopilotRuntime({
// 渠道会提供自己的Agent,因此此处运行时托管的Agent是可选的。
agents: {},
intelligence,
identifyUser: (request) => resolveUserFromSession(request),
channels: [support],
});Step 4: Mount a long-running host
步骤4:挂载长期运行的主机
typescript
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 });typescript
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));
// 此挂载可选,但值得执行:它会将激活失败转为启动失败,而非仅记录在日志中。
await listener.channels.ready({ timeoutMs: 30_000 });Whether you must call ready()
depends on the mount
ready()是否必须调用ready()
取决于挂载方式
ready()This is the single most misunderstood thing about Channels. Activation is not lazy everywhere.
| Mount | Activation | Is |
|---|---|---|
| Starts when the listener is created | No — await it to observe |
| Starts when the router is created | No — await it to observe |
| Deferred to the first | Yes |
| Deferred to the first | Yes |
any mount with | None; no control surface, no socket | Nothing will connect |
The two lifecycle-owning wrappers start on their own because they own their process lifetime — a declared Channel connects because it was declared, and there is no incantation to forget. The fetch and hono handlers defer on purpose: they are the serverless and edge entry points, where an isolate freezes and recycles per request and separate cold starts would mint competing listeners for the same Channel.
So the silent failure — a process that serves HTTP, looks healthy, and answers nothing — happens on a deferring mount when nothing ever calls . On a node or express host, a missing is not that failure.
ready()ready()Two details either way:
-
is one-shot and idempotent. It settles on the initial activation outcome, so awaiting it after an auto-start observes that activation rather than triggering a second one. It can resolve as
ready(), which means activation settled — not that delivery is healthy. Treat onlysetup_requiredas connected.status().overall === "online" -
Pair activation with shutdown so a redeploy releases the connection cleanly:typescript
process.on("SIGTERM", async () => { await listener.channels.stop(); process.exit(0); });
这是关于渠道最容易被误解的一点。激活并非在所有情况下都是惰性的。
| 挂载方式 | 激活时机 | 是否需要调用 |
|---|---|---|
| 创建监听器时启动 | 否——等待调用以观察状态 |
| 创建路由器时启动 | 否——等待调用以观察状态 |
| 延迟到首次调用 | 是 |
| 延迟到首次调用 | 是 |
任何设置 | 无激活;无控制面、无套接字 | 无法连接 |
两个生命周期管理的包装器会自行启动,因为它们拥有进程生命周期——声明的渠道会自动连接,无需额外操作。fetch和hono处理程序故意延迟激活:它们是无服务器和边缘入口点,其中隔离环境会在每个请求后冻结和回收,单独的冷启动会为同一渠道创建竞争的监听器。
因此,静默故障——进程提供HTTP服务,看起来正常,但完全不回复消息——会发生在延迟挂载且从未调用的情况下。在node或express主机中,缺失不会导致此故障。
ready()ready()无论哪种情况,有两个细节需要注意:
-
是一次性且幂等的。它会在初始激活结果时完成,因此在自动启动后等待调用会观察该激活状态,而非触发第二次激活。它可能会解析为
ready(),这表示激活已完成——而非交付正常。只有setup_required才表示已连接。status().overall === "online" -
将激活与关闭配对,以便重新部署时干净地释放连接:typescript
process.on("SIGTERM", async () => { await listener.channels.stop(); process.exit(0); });
Deployment shape
部署形态
Deciding whether the project already has a long-running host is the judgement this skill exists to make. Detect the framework first (see the copilotkit-setup skill's ), then:
references/framework-detection.md| What the project runs today | What to do |
|---|---|
| A standalone Node/Express/Hono server | Declare the Channel on the runtime it already has |
| Next.js on Vercel, Lambda, or an edge function | Add a separate long-running process for the Channel |
| A Python or .NET agent | Leave it alone; add a small TypeScript channel host that proxies |
A separate process is not mandated. If the project already runs a long-running runtime — one serving a React UI, for instance — that same runtime can declare Channels. A second process is required only when the existing runtime is serverless.
When a separate host is needed, it is a small program: the runtime, the Channel, and the awaited ready call. It does not need to serve the UI, and it does not need an HTTP server at all — nothing calls it. The gateway connection is outbound, and holding it open is what keeps the process alive. The scaffolded is exactly this, and is worth reading as the reference implementation even when writing one by hand.
channel-host.mts判断项目是否已有长期运行的主机是本技能的核心判断。先检测框架(请参阅copilotkit-setup技能的),然后:
references/framework-detection.md| 项目当前运行环境 | 操作建议 |
|---|---|
| 独立的Node/Express/Hono服务器 | 在现有运行时上声明Channel |
| Vercel、Lambda或边缘函数上的Next.js | 为Channel添加一个独立的长期运行进程 |
| Python或.NET Agent | 保持原Agent不变;添加一个小型TypeScript渠道主机进行代理 |
并非必须使用独立进程。如果项目已运行长期运行的运行时——例如服务于React UI的运行时——则同一运行时可声明渠道。只有当现有运行时是无服务器的,才需要第二个进程。
当需要独立主机时,它是一个小型程序:包含运行时、渠道和等待的ready调用。它无需服务UI,也无需HTTP服务器——没有任何内容会调用它。网关连接是出站的,保持连接打开是维持进程存活的方式。脚手架生成的正是如此,即使手动编写,也值得将其作为参考实现阅读。
channel-host.mtsNever do these
绝对禁忌
- Do not write provider credentials into the project for a managed Channel. Intelligence stores them server-side; the runtime never reads them. If the project holds a Slack bot token for a managed Channel, something is wired wrong.
- Do not put a Channel on a serverless route handler. It will appear to deploy and never connect.
- Do not assume is required, or that it is optional. Check the mount. Telling someone to add a call they do not need is as unhelpful as omitting one they do.
ready() - Do not pass alongside
runner. That is what silently keeps threads in memory.intelligence - Do not hand-wire a Channel into a scaffolded project. If is present the code half is done; adding a second declaration stops the host from booting rather than adding a bot.
channel-host.mts - Do not invent Channel infrastructure ids. Project, adapter, and channel ids are derived from the Intelligence config plus the Channel .
name - Do not send Teams icon or package bytes through Intelligence. Browser-generated artifacts stay local to the active attempt and are discarded afterward.
- Do not use Azure Bot for normal managed Teams setup. The launch path is Teams Developer Portal plus Entra, or the Teams-managed Fast CLI path.
- Do not call a Teams Channel working because setup says Created and installed. Provider completion and runtime/message health are separate axes.
- 不要将提供商凭证写入托管渠道的项目中。智能层会在服务器端存储凭证;运行时永远不会读取它们。如果托管渠道的项目持有Slack Bot令牌,则配置肯定有误。
- 不要将Channel放在无服务器路由处理程序上。它看起来会部署成功,但永远无法连接。
- 不要假设是必需的或可选的。请检查挂载方式。告诉用户添加不需要的调用和省略必需的调用一样无用。
ready() - 不要同时传递和
runner。这会导致线程静默地保留在内存中。intelligence - 不要手动将Channel对接至脚手架生成的项目中。如果存在,则代码部分已完成;添加第二个声明会导致主机无法启动,而非添加新Bot。
channel-host.mts - 不要自行发明渠道基础设施ID。项目、适配器和渠道ID是从智能层配置加上Channel的派生而来的。
name - 不要通过智能层发送Teams图标或包字节。浏览器生成的工件仅保留在当前尝试的本地,之后会被丢弃。
- 不要在常规托管Teams设置中使用Azure Bot。启动路径是Teams开发者门户加Entra,或Teams托管的快速CLI路径。
- 不要因为设置显示“已创建并安装”就认为Teams Channel正常工作。提供商完成与运行时/消息健康是独立的两个维度。