插件完整交付
这一页适合已经写好第一个命令、准备把插件交给别人使用的作者。目标不只是"代码能跑",而是别人能安装、能配置、出错时行为可预期、能在 Console 里观察到,并且能发布到 npm。
按下面六步走,每一步结尾都有可以自查的问题。
1. 先写清楚插件要做什么
动手前列出:用户输入什么、插件输出什么、有哪些副作用(写数据库、发消息、调外部 API),以及怎么验收。然后为每项能力选择一种写法:
| 需求 | 放在哪里 |
|---|---|
| 用户输入的命令 | commands/*/index.ts |
| 处理每条消息或平台事件 | middlewares/*/index.ts / handlers/*/index.ts |
| 卡片、图文等富消息 | components/*/index.tsx |
| AI 可调用的动作和背景知识 | tools/、prompt-sections/、skills/ |
| 数据库、定时任务、主动推送 | plugin.ts 的 setup,通过框架资源(Host token)取用 |
单个能力放约定目录;需要共享资源或生命周期的放 plugin.ts;只有要给整个生态定义一种新能力时才需要写 Feature。
2. 声明包结构
package.json#zhin声明插件包自身的入口(entry)和用到的 Feature(features);插件实例(适配器、子插件、服务)统一在宿主项目的zhin.config.yml的plugins:下挂载。zhin.config.yml声明挂载的插件实例与配置值,它不会安装包,也不会启用 Feature。- 发布到 npm 的插件入口必须是 JavaScript(
./plugin.js);本地项目可以直接用./plugin.ts。见 definePlugin · 开发入口与 npm 发布入口。 - 可选的能力(数据库、HTML 渲染等)声明为 optional peer,并在
setup里先用context.resources.has(token)(或在命令/工具里用context.has(token))判断,缺失时明确降级。
3. 设计配置
用 definePlugin({ config: Schema.object(...) }) 一次性声明默认值、类型、约束和 Console 表单信息,Schema.Output 会推导出配置类型;也可以用根目录的 schema.json(若同时保留由同一 Schema 生成、结构一致的 schema.json,运行时会校验二者等价并放行;isolated 插件只能用 schema.json)。见 definePlugin · 读取配置。
- 你的 Bot 自身读
plugin:段,子插件读<instanceKey>段。 - 密钥用
${VAR}引用环境变量,不要写进 Schema 默认值或示例仓库。
自查:修改某个配置后,是否需要重载插件?新配置非法时,旧配置是否继续生效?怎样在 Console 里确认新值已经生效?
4. 实现能力并管理生命周期
- 命令响应用户的明确调用;中间件按顺序处理入站消息;组件负责在不同平台都能显示的富消息。
- Tool 声明 AI 可以执行的动作;Prompt Section 只补充上下文,不能授予工具或数据权限。
- 打开的连接、定时器,以及框架资源注册返回的清理函数,都交给
lifecycle(context.lifecycle.add(...)),热重载时自动释放。 - 必须连接成功后才能允许切换到新版本的资源,用
handoff登记。新版本准备失败时,不能影响仍在服务的旧版本。
5. 本地验收
- 在 Sandbox 里把命令、组件和 AI 的最简单用法各走一遍。
- 在 Console 的"运行时能力"里核对每项能力的所属插件、来源、顺序和当前版本。
- 修改一个能力文件,确认只有它被重载;故意写错一次配置,确认旧版本继续服务。
- 对有副作用的 Tool,验证审批、取消和失败时的结果。
- 重启 Bot,确认数据库、会话和定时任务按预期恢复。
6. 测试与发布
插件至少需要:纯函数的单元测试、一个"能力能被发现"的测试、一次完整运行的集成测试,以及一个失败路径的测试。平台适配器还应使用 adapter harness 验证连接生命周期和消息转换。不想手工拼上下文、写 as never 时,用 插件测试 里的假运行环境。
pnpm build
pnpm check:plugin
pnpm check:plugin-runtime-api
pnpm check:plugin-capability-publish发布前检查 files、ESM 入口、peer dependency、约定目录生成的 JavaScript 以及 changeset;npx @zhin.js/cli plugin check 会自动检查其中的入口、files 与发布包结构(见 CLI 参考)。用打包后的产物安装到一个新项目里,再跑一次 Sandbox 的完整流程——不要只测试 workspace 里的源码。
zhin new --standalone 生成的独立插件在自己的目录里发布:
npx @zhin.js/cli plugin check # 检查入口、files、约定目录与打包结果
npx @zhin.js/cli pub --dry-run # 先自动运行 plugin check,再构建并预览 tarball 内容,不发布
npx @zhin.js/cli pub # 确认后发布;CI 等非交互环境加 --yes建议写 npx @zhin.js/cli:插件目录里没有安装 @zhin.js/cli 时,npx zhin 会从 npm 下载名为 zhin 的包。从 zhin@4 起它就是 @zhin.js/cli 的别名;但 npx 缓存或锁定里残留的 zhin@3.x 是已停止维护的旧版框架包。Bot 项目里装了 @zhin.js/cli,npx zhin 直接指向它。
插件市场元数据
插件市场(文档站 plugins.json、Console 插件市场、zhin search / zhin info)只读发布到 npm 的 package.json,不执行插件代码。下列字段决定市场里怎么展示你的插件:
| 字段 | 写法 | 市场里的用途 |
|---|---|---|
zhin.metadata.displayName / displayNameI18n | 同 Console;{ "zh-CN": "...", "en": "..." } | 卡片标题,按界面语言选择,回退顺序为当前语言 → en → zh-CN → displayName |
zhin.metadata.descriptionI18n | { "zh-CN": "...", "en": "..." },非空字符串 | 卡片描述,回退到顶层 description;搜索也会匹配两种语言的描述 |
zhin.metadata.category | 下表中的一个值 | 分类筛选。未声明时按包名与 keywords 推断 |
author | { "name": "...", "url": "..." } | 作者。市场显示 author,没有时显示 maintainers,不显示发布账号(CI 可信发布时它是「GitHub Actions」) |
keywords | 3–6 个描述功能的词 | 标签与搜索。zhin、zhin.js、zhinjs、zhin-plugin、bot、chatbot、plugin 属于通用词,市场不展示也不参与搜索;保留 zhin.js 方便在 npm 按关键词查找 |
descriptionI18n 和 category 只能写在 package.json#zhin.metadata,definePlugin() 不接受它们。清单在加载时严格校验:语言键只接受 zh-CN / en,category 必须是下列值之一:
category | 含义 | 例子 |
|---|---|---|
adapter | 平台适配器 | Telegram、OneBot 11、飞书 |
ai | AI 能力与推理服务 | TypeSafe 决策服务 |
admin | 运维管理 | 进程监控 |
community | 群组运营 | 群运营套件 |
entertainment | 娱乐互动 | 点歌、复读机、答案之书 |
game | 游戏 | 21 点、成语接龙 |
information | 资讯与订阅推送 | RSS、地震速报、B 站订阅 |
moderation | 内容安全 | 内容审核 |
service | 给其他能力用的基础服务 | 活动反馈 |
utility | 实用工具 | 二维码、短链接、记账、提醒 |
{
"name": "zhin.js-plugin-weather",
"description": "Weather forecasts and alerts for any city",
"keywords": ["zhin.js", "weather", "forecast", "天气"],
"author": { "name": "your-github-name", "url": "https://github.com/your-github-name/zhin.js-plugin-weather" },
"zhin": {
"protocol": 1,
"type": "plugin",
"metadata": {
"displayName": "Weather",
"displayNameI18n": { "zh-CN": "天气", "en": "Weather" },
"descriptionI18n": { "zh-CN": "查询城市天气预报与预警", "en": "Weather forecasts and alerts for any city" },
"category": "information"
}
}
}本仓库的官方插件还由 pnpm check:plugin 检查:必须有两种语言的 descriptionI18n、合法的 category、{ name, url } 形式且不含邮箱的 author,以及 3–6 个非通用 keywords。
发布前的完成标准
- 新用户照着 README 能完成安装、最小配置并收到第一条回复。
- 配置错误、依赖缺失和热重载失败都有明确的行为和提示。
- Console 能观察到运行状态,但不会泄露 Prompt 正文或密钥。
- 包内文档、公开 API、changeset 与实际发布的 tarball 一致。
下一步
- definePlugin:入口能做的所有事。
- 约定目录:每种能力放在哪里。
- Console 页面:给插件加一个管理页面。
- 本地联调指南:发布前在外部项目里联调本地插件与框架。