卡与插件的关系
卡只装声明
channel.json 里永远只有声明,不含插件实体。 插件按 id 匹配,住在哪取决于谁要用它:
| 插件 | 住在哪 | 卡里怎么写 | 适用 |
|---|---|---|---|
| 全局插件 | 用户插件目录,全机一份 | requires.plugins / requires.providers 按 id 引用 | 多个 频道都要用的能力 |
| 频道插件 | 频道文件夹里(推荐 plugins/<插件id>/) | plugins 按频道根内相对路径点名 | 只有这个频道用的东西 |
为什么不许把插件代码塞进卡本身:
- 「换一张卡」与「装一个插件」是两件独立的事,绑死后哪件都做不干净;
- 卡是纯数据、免代码信任门;一旦卡里能藏代码,这条安全边界就没了。
频道插件不破这条边界:它和卡同住一个文件夹,但卡只是点名,加载要过插件自己的门——
- 只认声明、不扫描:卡的
plugins没写的目录,放在频道里也不会被当成插件;路径必须在频道根内,不含..,解析真实路径后逃出频道文件夹的一律拒载; - 频道被信任之前一律不加载:打开一个带频道插件的频道,先出一张信任卡,逐个列出插件的信任级和它能做什么,三选「信任并加载 / 这次不加载 / 永不加载」;T2 插件信任频道之后仍要逐个批准;
- 防篡改:信任时记下代码指纹,代码变了就自动停用、要求重新确认。作者在自己的频道里改插件,可以在信任卡上勾「这是我自己的频道,打开开发模式」——改代码自动重新加载,不当成篡改。只给自己的频道开;
- 同一个 id 只能有一份:频道插件的 id 已被内置插件、全局插件或另一个开着的频道占用时拒载,并在频道里说明撞了谁;关掉占用它的那个频道窗口后会自动接上;
- 只在这个频道生效:别的频道看不见它,也不会被它污染。
一开始只有一个频道用的插件,放成频道插件最省事。等第二个频道也要用它,再把它搬成全局插件、两边改按 id 引用。
声明的三个层次
卡对「我需要什么全局能力」的表达,从粗到细有三层,都在 requires 段(详见requires 依赖声明):
| 层次 | 字段 | 说的是 |
|---|---|---|
| 要一类能力 | providers[].capability | 「我要 TTS」——不关心谁实现 |
| 偏好某个 provider | providers[].prefer | 「优先用这一家,别家顶上效果会不同」 |
| 指明去哪装 | providers[].plugins / plugins[] | 「装这个插件就能得到上面那个 provider」 |
这套分层让卡不锁死实现:收卡人机器上没有作者点名的那家 provider 时,系统可以按声明回落到别家(除非作者显式写 fallback: false), 并且知道该引导用户装哪个插件。
一起分发:组合包
导出 .channekcard 时,频道插件本来就是频道里的文件,随做法一起进包;全局插件则由打包器按 requires 把本机已装的一并收进包里。导入时拆包,卡进卡库、携带的全局插件走各自的安装与信任门;频道插件跟着开出来的新频道落盘,第一次打开那个频道时过信任卡——互不越界。
导入向导会逐个列出包内插件的 id、版本、信任级与权限清单,要求用户逐项确认——导入一张卡可能装上代码,绝不随卡静默安装。作为卡作者,别指望「藏在包里」能跳过用户的知情同意;作为收卡人,看到确认清单再点安装。
skill 跟着所有者走
给 AI Agent 看的方法论文档(skill)同样按归属分发:
- 「某个插件怎么用」的 skill,住插件包里,跟 插件走;
- 「某个频道特有的流程方法论」,住频道的 skill 目录里(卡的
agent.skills,推荐sop/skills),跟卡走。
打开频道时,应用把两处的 skill 摆到频道根的 .claude/skills/ 下,并把卡 agent 段声明的工作流说明与脚本目录写进频道根 AGENTS.md 的「先看这里」一节,供用户自己的 AI Agent 读取。执行主体是用户的 Agent,应用本身不执行 skill,也不运行这些脚本。 同名时卡里的优先——频道的具体打法覆盖插件的通用说明。