
Security News
PHP Foundation Says Chainguard Broke Security Embargo Ahead of Emergency Patches
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.
@cjhyy/code-shell-chat
Advanced tools
A standalone, transport-agnostic multi-channel chat gateway for Node.js and Bun.
可独立安装的 Node.js/Bun 多渠道 Chat Gateway。包本身不依赖 CodeShell runtime:第三方应用可以 直接组合 channel adapter、middleware、白名单和 webhook ingress,并使用自己的业务处理器。
bun add @cjhyy/code-shell-chat
import { ChatGateway, createAllowlistMiddleware } from "@cjhyy/code-shell-chat";
import { TelegramAdapter } from "@cjhyy/code-shell-chat/telegram";
const telegram = new TelegramAdapter({ botToken: process.env.TELEGRAM_BOT_TOKEN! });
const chat = new ChatGateway({ adapters: [telegram] });
chat.use(
createAllowlistMiddleware({
telegram: { targetIds: [process.env.TELEGRAM_CHAT_ID!] },
}),
);
chat.use(async ({ message, reply }) => {
await reply({ text: `echo: ${message.text}` });
});
const shutdown = new AbortController();
process.once("SIGINT", () => shutdown.abort());
await chat.run(shutdown.signal);
第三方也可以实现 ChannelAdapter 接入新平台,或用 middleware 接自己的 Agent、Webhook、工单系统与自动化服务。
配置驱动的 host 应使用异步工厂;它只会动态加载实际选择的平台模块:
import { createChannelAdapterAsync } from "@cjhyy/code-shell-chat/factory";
const telegram = await createChannelAdapterAsync({
channel: "telegram",
botToken: process.env.TELEGRAM_BOT_TOKEN!,
apiBaseUrl: "https://api.telegram.org",
allowedTargetIds: [process.env.TELEGRAM_CHAT_ID!],
allowedUserIds: [],
});
旧的同步 createChannelAdapter() 继续兼容,但返回的是首次 run、send 或 webhook 请求时
才加载具体平台的轻量代理。内置 CLI 和 Desktop 已使用异步入口,因此启动阶段会直接报告所选
adapter 的模块加载或构造错误。
当前各平台 SDK 仍是默认依赖:动态加载减少的是未启用平台的模块求值、副作用和启动开销,
不是安装体积。暂不改成 peer/optional dependency,是为了保证标准 bun add @cjhyy/code-shell-chat 和内置 CLI 安装后仍可直接启用任一支持渠道;后续若拆独立 adapter
包,需要同时提供安装检测和明确的按渠道安装指引。
包内置了可选的 @cjhyy/code-shell-chat/codeshell integration 和 code-shell-chat CLI。
在 CodeShell Desktop 中可直接进入「凭证 → Link → CodeShell Chat Gateway」查看全部支持渠道、
逐渠道连接状态和接入说明,一键打开可用平台的官方配置后台,启动/停止 gateway、编辑配置,
或扫码连接个人微信;卡片还会显示本次桌面运行中最近的收发消息。无需再单独打开终端运行 CLI。
钉钉渠道提供独立的结构化向导:填写应用凭据后可建立临时 Stream 连接,通过向机器人发送
测试消息自动发现 conversationId 和 senderStaffId,勾选后保存白名单并启动。Desktop 会把
Client Secret 放入 CodeShell 安全凭据库(系统支持时使用系统加密),config.json 只保留
Client ID 和白名单;旧版配置中的
明文 Secret 会在首次使用新向导保存时迁移。独立 CLI 继续支持配置文件或
CODE_SHELL_DINGTALK_CLIENT_SECRET 环境变量。
CLI 会把普通文字和附件转到桌面端的 Mimi Pet 长期会话。Mimi 先通过只读 Gateway 工具发现
本进程已启用的渠道及某一渠道的精确能力,需要回复时再用 GatewayReply 把结果送回原 IM
会话。
不同渠道继续共享 Mimi 的长期上下文,但每条入站消息会标明来源渠道,当前消息来源也会进入
Mimi 的运行时上下文,避免多渠道同时使用时无法分辨入口。
/open、/close、/status 仍作为确定性快捷入口;“帮我打开手机遥控”“关闭公网入口”
和“看看手机遥控状态”等明确自然语言也会触发相同操作。mobile remote 继续复用 tunnel +
pairing + passcode,并把 10 分钟一次性配对入口作为按钮回到原会话。
富媒体纵向链路现在覆盖绝大多数内置渠道。附件在 sender/target 白名单通过后才按需下载,
受数量、单文件、总大小和超时限制;随后由 Electron main 校验并暂存到 Pet 会话的
.code-shell/attachments 目录。Mimi 仅在对应 IM 回合显式调用 GatewayReply 后,由宿主
校验并发送项目、no-repo 或用户 Downloads 目录中的本地图片、文件、音频或视频;任务
产物记录本身不会触发回传。
CLI 不直接拥有 cloudflared 或 mobile WebSocket host。Electron main 仍是生命周期唯一
owner,并通过 ~/.code-shell/im-gateway/desktop-control.json(0600)提供随机 bearer token
保护的 loopback control API。CLI 与 Desktop 使用同一个原子进程锁,不能同时消费平台更新。
所有渠道可以在同一 gateway 进程中同时启用,每个渠道有独立的会话和用户白名单。
| 渠道 | 官方接入方式 | 公网 webhook |
|---|---|---|
| Telegram | Bot API getUpdates 长轮询 | 不需要 |
| Discord | Gateway + REST | 不需要 |
| Slack | Socket Mode + Web API | 不需要 |
| 飞书 / Lark | 官方 SDK WebSocket 长连接 | 不需要 |
| 钉钉 | Stream Mode | 不需要 |
| 个人微信 | 腾讯 ClawBot iLink + 扫码登录 | 不需要 |
| 企业微信 | 智能机器人 WebSocket 长连接 | 不需要 |
| Matrix | Client-Server API /sync | 不需要 |
| Mattermost | WebSocket events + REST | 不需要 |
| LINE | Messaging API webhook | 需要,/webhooks/line |
| WhatsApp Business | Meta Cloud API webhook | 需要,/webhooks/whatsapp |
| Microsoft Teams | Bot Framework messaging endpoint | 需要,/webhooks/teams |
下表描述的是 CodeShell 当前适配器已经实现并经过测试的能力,不代表上游平台 API 的全部潜在
功能。Gateway 通过 ChannelAdapter.capabilities 把同一份声明用于 QR、附件过滤、主动通知和
Pet 的两层工具;Pet 不再按渠道名猜测能力。
| 渠道 | 入站附件 | 出站文字/链接 | 出站附件 | 路由/实现限制 |
|---|---|---|---|---|
| Telegram | 图片、文件、音频、视频 | 文字、原生按钮 | 图片、文件、音频、视频 | 已知 chat 可回复或主动推送 |
| Discord | 图片、文件、音频、视频 | 文字、原生按钮 | 图片、文件、音频、视频 | 需 Attach Files;已知 channel |
| Slack | 图片、文件、音频、视频 | 文字、原生按钮 | 图片、文件、音频、视频 | 需 files:read/files:write;已知 channel |
| 飞书 / Lark | 图片、文件、音频、视频 | 文字、原生按钮 | 图片、文件、音频、视频 | 需消息资源读取和图片/文件上传权限 |
| 钉钉 | — | 文字、ActionCard | — | Stream 临时 session webhook 不承载本地媒体 |
| 企业微信 | 图片、文件、视频 | 文字、Markdown 链接 | 图片、文件、音频、视频 | 入站语音当前协议只提供转写;出站音频按文件发送 |
| 个人微信 | 图片、文件、音频、视频 | 文字、普通链接 | 图片、文件、音频、视频 | 音频按可播放文件发送;依赖 context_token |
| Matrix | 图片、文件、音频、视频 | 文字、HTML 链接 | 图片、文件、音频、视频 | 当前媒体管线不解密 E2EE 附件 |
| Mattermost | 图片、文件、音频、视频 | 文字、Markdown 链接 | 图片、文件、音频、视频 | 需 bot 文件上传权限 |
| LINE | 图片、文件、音频、视频 | 文字、按钮模板 | — | 出站媒体需另配公网 HTTPS 媒体存储,未虚报为已支持 |
| WhatsApp Business | 图片、文件、音频、视频 | 文字、CTA 按钮 | 图片、文件、音频、视频 | 受 customer-care window 约束 |
| Microsoft Teams | 图片、文件、音频、视频 | 文字、Markdown 链接 | 图片 | Bot Framework 内联图片;其他文件需另接 Graph/SharePoint 流程 |
出站附件统一限制为每次最多 4 个、通常单个最多 10 MiB;Teams 内联图片遵循平台的 1 MiB 上限。适配器遇到未声明的附件类型会明确报错,Gateway 在调用适配器前会按能力过滤 Desktop/Pet 产物。
Pet 使用分层、原子化工具:
Gateway:search 只在本回合实际授予的渠道中查询,不带 query 时返回全部渠道名,
也可用 outbound:image、inbound:file、button:native 等条件过滤;describe 再返回一个
匹配渠道的精确入站/出站契约。能力目录每回合从当前适配器集合重建,只含渠道名和能力,
不传递 token、白名单或会话 target。GatewayReply:只绑定当前入站消息的原会话,一次声明完整文字、可选 URL 按钮
和可选本地附件。它不能借第一层目录向其他渠道或其他 target 任意发送。SendMessage:用于桌面 Mimi 主动发往宿主授权的 owner 目标。模型只看到宿主生成
的不透明 target_id,看不到原始平台用户 ID;宿主在执行前重新读取白名单并校验文字、附件与
渠道能力。工具调用只登记请求,Mimi 的原始回复会被宿主的实际“平台已接受”或失败回执替换;
平台接受仍不等于收件人设备已经展示消息。普通纯文字回复可以直接进入第二层;富媒体能力不确定、或用户询问另一个已启用渠道时,Mimi
先查询第一层。Gateway 按当前路由动态移除 GatewayReply 不支持的附件参数,并在真正调用
适配器前再次校验。
工具文字最多 8000 字,Gateway 使用 1800 字的跨渠道安全分片发送,按钮和附件只跟随最后一片;
中途失败时从未发送的分片续传,不会重新运行 Pet 或重复已经送达的前半段。需要多次
平台 API 调用的文字+媒体组合也会记录已完成子步骤,同一进程内重试时从失败子项继续。
个人微信使用腾讯 ClawBot 公开的 iLink Bot 协议,不接逆向 Web 协议。首次使用先执行:
code-shell-chat wechat login
终端会显示二维码,用手机微信扫码并确认。token 保存在 owner-only 凭据文件中,不写入
gateway 配置。登录命令会自动增加 wechat.accountId,扫码者默认成为唯一允许的用户。
@cjhyy/code-shell-chat/wechat 同时导出 loginWechatWithQr、WechatAdapter、
sendWechatDirect 和 FileWechatCredentialStore,第三方应用可以完全不使用 CodeShell CLI。
sendWechatDirect 是供定时任务和通知 worker 使用的独立入口:同一进程里若已有相同账号与
凭据的长轮询 adapter,它会排队复用该实例;否则才创建一次性 adapter。同一账号只允许
一个 live adapter;所有 adapter 实例的发送、限流冷却和生命周期请求共用账号级串行边界。
运行中若更换 token,必须先重启 Gateway,不会用新旧凭据打开竞争会话。CodeShell Desktop 的
扫码流程也把游标和上下文视为凭据会话状态:token 或服务地址变化时清空旧状态;凭据未变时保留
有效状态,而一次用户主动发起且平台确认成功的登录(含“已绑定”确认)可以修复损坏或不安全的
状态文件。普通启动仍然失败关闭。
Desktop 宿主包装还会在整个直发期间持有与 CLI 共用的 Gateway 单实例锁;若锁已由 CLI 持有,通知会
留给 CLI 的事件 watcher,不会再开一条短连接。这样可避免并行连接得到 HTTP 成功却没有在微信
客户端显示。返回值区分
status: "accepted"、terminalDeliveryConfirmed: false 和 viaLiveAdapter,调用方不得把它
渲染成“已送达”。
import { FileWechatCredentialStore, sendWechatDirect } from "@cjhyy/code-shell-chat/wechat";
const store = new FileWechatCredentialStore();
const credentials = store.load("my-account");
if (!credentials?.userId) throw new Error("请先完成微信扫码登录");
const receipt = await sendWechatDirect(
credentials,
credentials.userId,
{ text: "这是一条定时提醒" },
{ stateStore: store.stateStore(credentials.accountId) },
);
// receipt.status === "accepted" 只表示平台接口接受,不代表收件设备已展示。
这层路由直接借鉴 Hermes 的
send_weixin_direct:
先按凭据查找同事件循环里的 live adapter,找不到时才创建短生命周期 adapter,并让两条路径复用
同一套上下文、媒体上传和响应校验。CodeShell 把这个做法收敛为渠道 adapter 的 send() 能力和
宿主单实例锁,而不是让定时任务直接依赖微信模块。
个人微信的公开协议契约仍以 context_token 为主。adapter 会持久保存每个 owner 最近一次入站
消息带来的 token;遇到明确的过期响应时清除它,并用同一个 client_id 做一次无 token 的兼容
尝试,但该降级并非所有账号都支持。CodeShell Desktop 因此只在 owner 先给 Mimi 发过一条消息、
且状态中存在可用 token 时向 SendMessage 暴露微信目标;若返回 prepare failed,界面会要求
先从微信发一条新消息刷新上下文,而不会继续宣称推送成功。
入站长轮询只在上层持久接收消息后才提交 get_updates_buf,临时处理失败会保留旧游标重投。
媒体下载只允许已知微信 CDN 的 HTTPS 主机,并逐跳重新校验重定向。
桌面任务和定时通知只发布渠道无关的事件;Gateway 停止时,宿主会用同一套独立直发能力处理
支持 direct 的目标,Gateway 在线时则由事件 watcher 独占发送。两者共享事件进度,启动交接不会
重复发送;独立直发完整成功时可直接确认当前队首,不必等 watcher 以后上线才释放容量。进度文件在事件游标旁保留最近
200 个事件、每个目标的文本分段和附件子步骤;文件使用 owner-only 权限和原子替换,
符号链接、过大或损坏状态会失败关闭,不能伪造“已投递”进度。单个
渠道失败或 Gateway 正常重启时只续传未完成部分。传输已受理但未来得及落盘就崩溃的窗口仍是
「至少一次」,不会伪称精确一次。
控制事件本身也会在对 watcher 可见前,先原子写入 owner-only outbox(最多 200 条未确认事件)。
Desktop 重启后恢复同一 streamId 和递增事件 ID,所以 Gateway 会从已确认游标继续,而不是
把重启前尚未接收的通知遗留在内存里。落盘失败时发布直接失败,不会暴露半成功事件。
watcher 在下一次请求带回持久游标后,Desktop 才原子删除已确认前缀;未确认队列满时施加背压并拒绝
新发布,不会静默挤掉旧通知。如果恢复后发现本地游标高于 outbox,协议会显式要求 watcher 重置,
且 checkpoint 必须先写盘成功,内存游标才能前进。
每个 live watcher 事件在真正调用 adapter 前还会重读当前配置验证 target 白名单;用户已撤销的目标会被安全跳过,
不会因为 Gateway 启动时的旧快照继续发送。
长任务 closure、Work Session 主动上报和定时任务终态都会携带匿名稳定 delivery key;Desktop
控制事件若在上游恢复时被重新发布,仍会接续原目标/分段/附件进度,而不是把它当成全新通知。
定时任务的完成、失败、永久停用和用户取消分别发布 automation.completed、
automation.failed、automation.stopped 和 automation.cancelled;开始事件不外发,避免每次
执行产生两条通知。
协议实现对齐 Tencent/openclaw-weixin 2.4.x 的公开文档。
创建 ~/.code-shell/im-gateway/config.json 并设置为 0600。只需保留实际启用的平台:
Desktop 的「编辑配置」会生成包含所有渠道的模板;在模板里把要使用的渠道设为
"enabled": true 并填写凭据和白名单即可,未启用的占位配置不会加载。
{
"telegram": {
"botToken": "123456:replace-me",
"allowedChatIds": ["123456789"],
"allowedUserIds": ["123456789"]
},
"discord": {
"botToken": "replace-me",
"allowedChannelIds": ["channel-id"],
"allowedUserIds": ["user-id"]
},
"slack": {
"botToken": "xoxb-replace-me",
"appToken": "xapp-replace-me",
"allowedChannelIds": ["C0123456789"],
"allowedUserIds": ["U0123456789"]
},
"lark": {
"appId": "cli_replace_me",
"appSecret": "replace-me",
"allowedChatIds": ["oc_chat_id"],
"allowedUserIds": ["ou_open_id"]
},
"dingtalk": {
"clientId": "replace-me",
"clientSecret": "replace-me",
"allowedConversationIds": ["cid_replace_me"],
"allowedUserIds": ["staff-id"]
},
"wecom": {
"botId": "replace-me",
"secret": "replace-me",
"allowedChatIds": ["chat-id-or-user-id"],
"allowedUserIds": ["user-id"]
},
"wechat": {
"accountId": "generated-by-wechat-login"
},
"matrix": {
"homeserverUrl": "https://matrix.example.com",
"accessToken": "replace-me",
"botUserId": "@codeshell:example.com",
"allowedRoomIds": ["!room:example.com"],
"allowedUserIds": ["@owner:example.com"]
},
"mattermost": {
"serverUrl": "https://mattermost.example.com",
"botToken": "replace-me",
"botUserId": "bot-user-id",
"allowedChannelIds": ["channel-id"],
"allowedUserIds": ["owner-user-id"]
},
"line": {
"channelSecret": "replace-me",
"channelAccessToken": "replace-me",
"allowedTargetIds": ["group-room-or-user-id"],
"allowedUserIds": ["owner-user-id"]
},
"whatsapp": {
"accessToken": "replace-me",
"appSecret": "replace-me",
"verifyToken": "choose-a-random-value",
"phoneNumberId": "replace-me",
"apiVersion": "v25.0",
"allowedPhoneNumbers": ["8613800000000"]
},
"teams": {
"appId": "replace-me",
"appPassword": "replace-me",
"appType": "MultiTenant",
"allowedConversationIds": ["conversation-id"],
"allowedUserIds": ["aad-or-channel-user-id"]
},
"webhook": {
"host": "127.0.0.1",
"port": 8787,
"maxBodyBytes": 1048576
},
"runtime": {
"maxPending": 1000,
"maxConcurrent": 4,
"maxPerTarget": 1,
"maxMessagesPerUserPerMinute": 20
},
"notifications": {
"enabled": true,
"targets": {
"telegram": ["123456789"]
}
}
}
每个已启用渠道都必须配置 target 白名单;allowedUserIds 可选,但群聊强烈建议配置。
未知会话和用户会被静默忽略。配置文件包含 bot secret、允许远程拉起桌面端的命令,非 Windows
系统只接受 0600 权限。
可序列化的文字入站会先原子写入 ~/.code-shell/im-gateway/inbox.json,webhook 平台随后才会
收到确认;同一账号的相同平台 message ID 会持久去重。处理采用有界队列、全局并发和 per-target
串行,暂时失败按指数退避重试。附件字节先写入 inbox 旁的 spool 再确认;spool 本地写入失败时
拒绝确认,交由平台重投。超出 spool 上限或下载失败的媒体保留在内存队列,仍受平台重投策略约束。
notifications.targets 最多 32 个,只能引用已启用渠道且必须同时位于该渠道 target 白名单。Desktop 主动
通知游标会按事件流实例原子写入 ~/.code-shell/im-gateway/desktop-events.json(可用
runtime.eventCursorPath 覆盖);Gateway 和 Desktop 正常重启后都会续传。只有 Desktop 初始化了全新的
事件 outbox 时,watcher 才会识别新 streamId 并安全重置游标。
所有字段都有 CODE_SHELL_<CHANNEL>_* 环境变量对应项;数组使用逗号分隔。例如:
export CODE_SHELL_DISCORD_BOT_TOKEN='replace-me'
export CODE_SHELL_DISCORD_ALLOWED_CHANNEL_IDS='channel-1,channel-2'
export CODE_SHELL_DISCORD_ALLOWED_USER_IDS='owner-user-id'
也可用 CODE_SHELL_IM_GATEWAY_CONFIG 指定配置路径。完整环境变量名以仓库中的
packages/chat/src/config.ts
为准。
LINE、WhatsApp、Teams 要求一个固定公网 HTTPS origin。gateway 默认仅监听
127.0.0.1:8787,应由同机的反向代理或固定隧道暴露上表中的 path。手机遥控使用的
Cloudflare quick tunnel 指向 mobile host、URL 也会变化,不能当作这些平台的 webhook origin。
x-line-signature;x-hub-signature-256;本地探针为 GET /healthz 与 GET /readyz;探针不会由生成的公网 ingress 暴露。生成固定
HTTPS 反向代理配置:
code-shell-chat ingress print --host chat.example.com --format caddy
# 或 --format nginx;upstream 只允许 loopback host:port
/open、/close、/status
application commands;chat:write;im.message.receive_v1,选择长连接接收事件;code-shell-chat wechat login,支持二维码确认和手机数字验证;getUpdates 互斥;桌面端需要先设置 mobile remote 访问口令。macOS 默认用 open -a code-shell 拉起离线桌面端;
其他安装方式可覆盖 desktop.command 和 desktop.args。
bun run --filter '@cjhyy/code-shell-chat' build
node packages/chat/dist/cli.js
也可以直接开发运行:
bun run --filter '@cjhyy/code-shell-chat' dev
安装为当前用户的常驻服务(macOS launchd、Linux systemd user、Windows Task Scheduler):
code-shell-chat service install
code-shell-chat service status
code-shell-chat service uninstall
服务定义不写入 secret,只引用 owner-only 配置;运行时单实例锁会阻止 Desktop 与 daemon 双开。
真实平台发布前运行凭据 canary。命令会生成随机 nonce;在每个启用 channel 的白名单会话中发送
提示的 /canary <nonce>,只有真实入站、验签/鉴权、allowlist 和真实回发全部成功才会通过:
code-shell-chat canary --timeout-ms 600000
FAQs
A standalone, transport-agnostic multi-channel chat gateway for Node.js and Bun.
The npm package @cjhyy/code-shell-chat receives a total of 280 weekly downloads. As such, @cjhyy/code-shell-chat popularity was classified as not popular.
We found that @cjhyy/code-shell-chat demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.

Security News
PHP releases emergency patches for two critical RCE vulnerabilities as agencies urge organizations to take unpatched sites offline.

Threat Research
/Security News
A supply chain attack compromised LiteLLM 1.107.0 on PyPI and Docker, spreading malware that steals cloud credentials, developer secrets, and AI provider keys.