| 1 | # 编写你的第一个 Codewhale 插件 |
| 2 | |
| 3 | > 英文原文:[PLUGIN_AUTHORING.md](../PLUGIN_AUTHORING.md)。 |
| 4 | > 最后与英文同步日期(last synced with English revision):2026-09-29。 |
| 5 | |
| 6 | 先从一个 skill 开始:把 Markdown 指令文件放进一个小型插件包。 |
| 7 | [hello-codewhale 示例](../examples/plugins/hello-codewhale/plugin.json) |
| 8 | 只有两个文件,不声明服务器或 hook,也不要求调用工具。 |
| 9 | 本指南带你从源文件开始,完成审查、启用和调用。 |
| 10 | |
| 11 | ## 1. 创建插件包 |
| 12 | |
| 13 | 直接使用仓库中的示例,或在已安装插件目录之外创建以下目录: |
| 14 | |
| 15 | ```text |
| 16 | hello-codewhale/ |
| 17 | ├── plugin.json |
| 18 | └── skills/ |
| 19 | └── hello/ |
| 20 | └── SKILL.md |
| 21 | ``` |
| 22 | |
| 23 | `plugin.json`: |
| 24 | |
| 25 | ```json |
| 26 | { |
| 27 | "$schema": "https://agent-plugins.org/schemas/plugin.json", |
| 28 | "name": "hello-codewhale", |
| 29 | "version": "0.1.0", |
| 30 | "description": "A minimal, explicitly invoked greeting skill." |
| 31 | } |
| 32 | ``` |
| 33 | |
| 34 | `skills/hello/SKILL.md`: |
| 35 | |
| 36 | ```markdown |
| 37 | --- |
| 38 | name: hello |
| 39 | description: Greet the user when they explicitly try the hello-codewhale example. |
| 40 | invocation: explicit-only |
| 41 | --- |
| 42 | |
| 43 | Respond with one short greeting in the user's language. Include the exact text |
| 44 | `hello-codewhale:hello` so they can identify the example they invoked. |
| 45 | |
| 46 | Use only the conversation. Do not call tools, run commands, read or write files, |
| 47 | or contact external services. |
| 48 | ``` |
| 49 | |
| 50 | 这里保留与仓库一致的英文示例;指令要求用用户的语言打招呼,并包含 |
| 51 | `hello-codewhale:hello`,同时不调用工具、不运行命令、不读写文件、不联系外部服务。 |
| 52 | Codewhale 会自动发现 `skills/`。`explicit-only` 表示此示例不进入模型的自动 |
| 53 | skill 目录,而是由你按名称加载。调用元数据见 |
| 54 | [Skills](./SKILLS.md#调用与别名元数据),清单的权威格式以 |
| 55 | [插件包契约](./PLUGIN_BUNDLES.md#清单)为准。 |
| 56 | 新原生插件使用 `plugin.json` 即可,无需再写另一份清单。 |
| 57 | |
| 58 | ## 2. 安装、检查并信任 |
| 59 | |
| 60 | 在仓库根目录启动 Codewhale,然后在 **Codewhale 会话内**逐条输入: |
| 61 | |
| 62 | ```text |
| 63 | /plugin install ./docs/examples/plugins/hello-codewhale |
| 64 | /plugin validate hello-codewhale |
| 65 | /plugin show hello-codewhale |
| 66 | ``` |
| 67 | |
| 68 | 如果使用自己的插件包,把安装路径替换为对应目录。安装会将插件复制到 |
| 69 | `~/.codewhale/plugins/hello-codewhale/`,初始状态为未启用、未信任。 |
| 70 | 审查已安装的源文件、skill 清单和权限。此示例应只声明 Skills, |
| 71 | 没有 MCP 服务器、hook 或申请访问的网络主机。 |
| 72 | |
| 73 | 安装审查会打印一条包含两个完整哈希的命令: |
| 74 | |
| 75 | ```text |
| 76 | /plugin trust hello-codewhale <full-content-sha256>.<full-capability-sha256> |
| 77 | ``` |
| 78 | |
| 79 | 审查后执行实际打印的完整命令;上面的尖括号内容只是占位符。 |
| 80 | 需要重新查看审查内容时,输入不带令牌的 `/plugin trust hello-codewhale`。 |
| 81 | 信任操作记录已审查的内容哈希和能力哈希,并创建运行时快照,尚不会启用插件。 |
| 82 | |
| 83 | ```text |
| 84 | /plugin enable hello-codewhale |
| 85 | /skills hello-codewhale: |
| 86 | /skills inspect |
| 87 | ``` |
| 88 | |
| 89 | skill 的名称是 `hello-codewhale:hello`,即插件名加 skill 名。 |
| 90 | `/skills inspect` 会标明它来自已审查的插件快照。 |
| 91 | |
| 92 | ## 3. 调用并停用 |
| 93 | |
| 94 | ```text |
| 95 | /skill hello-codewhale:hello |
| 96 | ``` |
| 97 | |
| 98 | Codewhale 确认激活后,再发送普通消息 `请打个招呼。`。 |
| 99 | 回复应是一句简短问候,并包含 `hello-codewhale:hello`。 |
| 100 | 示例只提供指令;回复仍使用你选择的模型及其正常的提供商连接。 |
| 101 | 本地安装、审查和激活步骤本身不需要调用模型。 |
| 102 | |
| 103 | ```text |
| 104 | /plugin disable hello-codewhale |
| 105 | ``` |
| 106 | |
| 107 | 停用会移除插件提供的功能,但保留信任记录。之后执行 |
| 108 | `/skill hello-codewhale:hello` 不应再激活该 skill。 |
| 109 | 只要已审查的哈希仍然匹配,就可以在需要时重新启用。 |
| 110 | |
| 111 | ## 4. 修改并重新审查 |
| 112 | |
| 113 | 已安装的插件是副本。修改示例的原始源文件不会更新该副本。 |
| 114 | 要尝试修改后的本地源文件,先停用并卸载已安装的示例,再重新安装源目录: |
| 115 | |
| 116 | ```text |
| 117 | /plugin disable hello-codewhale |
| 118 | /plugin uninstall hello-codewhale |
| 119 | /plugin install ./docs/examples/plugins/hello-codewhale |
| 120 | /plugin validate hello-codewhale |
| 121 | ``` |
| 122 | |
| 123 | 卸载只删除已安装的副本,保留原始示例源文件。审查新的令牌,确认信任后再启用。 |
| 124 | 从远程来源安装的插件使用 `/plugin update <name>`;详情见 |
| 125 | [插件安装指南](./PLUGINS.md#更新与卸载)。 |
| 126 | |
| 127 | 直接修改已发现的插件包内的文件后,使用 `/plugin reload` 刷新注册表。 |
| 128 | 即使版本号没变,内容变化也会使旧信任记录失效。重新加载不会授予信任。 |
| 129 | 要主动撤销信任,使用 `/plugin revoke <name>`。 |
| 130 | |
| 131 | ## 只添加需要的组件 |
| 132 | |
| 133 | 所有组件都使用同一套插件审查流程和现有 Codewhale 运行时: |
| 134 | |
| 135 | | 组件 | 编写方式 | |
| 136 | | --- | --- | |
| 137 | | Skills | `skills/<name>/SKILL.md`;参见[指令与调用契约](./SKILLS.md)。 | |
| 138 | | MCP | 与清单同级的 `mcp.json`;参见[插件传输与凭据规则](./PLUGIN_BUNDLES.md#校验两种格式)。 | |
| 139 | | Commands | Markdown 命令文件;参见[命令元数据](../architecture/command-dispatch.md#user-commands)。 | |
| 140 | | Agent profiles | Fleet TOML 配置文件;参见[Fleet 编写指南](./FLEET.md#编写-agent-配置fleet-setup)。 | |
| 141 | | Hooks | `HooksConfig` TOML 文件;参见[事件与进程行为](./HOOKS.md)。 | |
| 142 | | Native mod(实验性) | 已审查的 ESM 入口,可注册工具、命令、执行前监听器、提示词片段及插件本地 JSON 状态;参见[扩展契约](./EXTENSIONS.md)。 | |
| 143 | |
| 144 | Commands、Agents 和 Hooks 的路径声明放在 `plugin.json` 的 |
| 145 | `extensions["net.codewhale"]` 中,详见 |
| 146 | [插件组件契约](./PLUGIN_BUNDLES.md#生效与未生效的组件面)。 |
| 147 | 不要把 MCP 服务器字段或任意运行时入口放在清单根级。 |
| 148 | LSP 可以列入清单,但目前没有可执行的适配器。`native` 原生扩展默认只列入清单; |
| 149 | 开启实验性的 `[features] extension_host` 后,它必须指向一个 `.mjs`、`.js` 或 `.mts` |
| 150 | ES 模块文件,由 TypeScript 扩展宿主运行,`/plugin validate` 会拒绝其他入口。 |
| 151 | 其工具始终使用 `Required` 审批要求,不采信插件自行声明的只读提示; |
| 152 | Full Access(完全访问)、Bypass,或针对该已审查构建的精确会话授权,都可以满足这一要求而不再弹出审批 |
| 153 | ([设计文档](../design/TS_EXTENSION_HOST.md#as-built-phase-1-2026-09-25))。 |
| 154 | 可运行的类型化示例、生命周期和按插件归属显示的诊断见 |
| 155 | [扩展编写指南](./EXTENSIONS.md)。`.mts` 只支持 Node 可直接擦除的类型语法,无需单独的编译器;需要转换的语法不受支持。 |
| 156 | |
| 157 | ## 编写有明确作用范围的 Native mod |
| 158 | |
| 159 | [mod-extension 示例](../examples/plugins/mod-extension/README.md) 是可运行的 |
| 160 | ESM 插件包,不需要安装依赖或运行编译器。它注册 `mod_counter` 工具、 |
| 161 | `/mod-count` 用户命令、一个提示词片段,以及只处理自身工具的执行前监听器。 |
| 162 | 工具返回结构化 JSON 计数结果;`ctx.storage` 将计数保存在 Rust 指定的插件目录中。 |
| 163 | 清理函数可重复调用,会撤销注册并等待排队的工作结束。停用后监听器和提示词片段 |
| 164 | 被撤回,计数状态仍然保留。 |
| 165 | |
| 166 | 先明确开启实验性的 `extension_host` 功能,再安装示例目录,验证并查看其 Native |
| 167 | 能力。由本人审查源代码和精确的内容/能力哈希,执行审查给出的信任命令后再启用。 |
| 168 | 功能关闭时,Native 代码只列入清单,不执行。源文件变更需要重新审查; |
| 169 | `/plugin reload` 是手动刷新,不是自动监视或热重载。 |
| 170 | |
| 171 | 可用的编写服务包括 `tools`、`commands`、`prompt`、`storage`、`logger` 和 Cordis |
| 172 | 生命周期机制。`ctx.on('tools/pre-execute', ...)` 可以返回不干预、拒绝、请求审批、 |
| 173 | 修订对象输入或补充上下文。Rust 合并这些提议;输入变更后重新准备调用并检查权限。 |
| 174 | `allow` 不授予批准,`next()` 只表示不干预。异常、格式错误、超时及已撤销的所有者 |
| 175 | 都会使当前调用被拒绝。这是执行前提议契约,不提供包裹实际执行的中间件,也不支持 |
| 176 | 改写执行后的结果。 |
| 177 | |
| 178 | `ctx.prompt.registerSection({id, text})` 提交有边界、带来源的指令,通过现有 Engine |
| 179 | 运行时消息进入上下文。`ctx.storage.get/set/delete` 保存有大小限制的插件本地 JSON, |
| 180 | 可跨代保留。两者都不能替换系统提示词、会话存储或凭据服务。工具和命令调用可获得 |
| 181 | 本次调用的只读 `sessionId`、`agentId`、`originTurnId` 字符串,Rust 未提供时字段缺省; |
| 182 | 这些是标识标签,不是运行时操作句柄。目前没有已发布的插件编写 SDK,示例使用文档 |
| 183 | 规定的宿主适配服务。 |
| 184 | |
| 185 | 自定义 Ratatui/GPUI 控件、DSH 浏览器 UI 插槽、skill 根目录注册、原生执行 |
| 186 | `dsh.bundle.patch` 及 DSH 自身的 agent 运行时尚未提供。下文的静态导入器仍只转换 |
| 187 | 可移植子集。兼容的 Claude 插件包仍使用原有的声明组件适配器;此 Native API 不加载 |
| 188 | Claude 的 agent loop,也不会自动适配 Pi 扩展 API。可执行 mod 需按文档中的宿主契约 |
| 189 | 移植。扩展工具只有在模型直接调用它、且共享 turn gate 正在处理本次调用时, |
| 190 | 才能使用 `exec.core.call`。命令及激活代码没有 core 句柄。嵌套 shell 和网络调用必须 |
| 191 | 弹出用户审批;无法打开审批的模式会拒绝执行。完整的拒绝列表、取消语义和调用上限 |
| 192 | 见[请求核心执行工具](./EXTENSIONS.md#请求核心执行工具)。 |
| 193 | |
| 194 | 插件信任**不是操作系统沙箱**。本地 MCP 服务器或 hook 可以启动进程; |
| 195 | 启用前需审查其代码和权限。Skills 不授予权限:仓库指令、权限规则、沙箱策略 |
| 196 | 和工具审批仍然适用。不要在插件包或命令参数中保存凭据。 |
| 197 | MCP 使用[插件验证契约](./PLUGIN_BUNDLES.md#校验两种格式)规定的、 |
| 198 | 经过审查的环境变量引用;添加 hook 前,请阅读独立的 |
| 199 | [hook 环境契约](./HOOKS.md#hook-进程环境)。 |
| 200 | |
| 201 | ## 转换现有插件 |
| 202 | |
| 203 | [`scripts/convert-plugin.py`](../../scripts/convert-plugin.py) 将明确选定的远程 MCP |
| 204 | 声明、已打包的本地 Node MCP 服务器和可移植 Skills 转换为原生插件包。 |
| 205 | 它需要 Python 3.10+ 和 PyYAML 6+;如果缺少,请单独安装。转换器不会安装依赖、扫描现有应用配置或凭据、 |
| 206 | 发送网络请求,也不会执行源代码。 |
| 207 | |
| 208 | ### OpenCode |
| 209 | |
| 210 | 将以下纯 JSON 保存为 `opencode-mcp.json`: |
| 211 | |
| 212 | ```json |
| 213 | { |
| 214 | "mcp": { |
| 215 | "docs": { |
| 216 | "type": "remote", |
| 217 | "url": "https://example.invalid/mcp", |
| 218 | "oauth": false, |
| 219 | "enabled": false |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | ``` |
| 224 | |
| 225 | 在 Codewhale 仓库根目录的 shell 中运行: |
| 226 | |
| 227 | ```sh |
| 228 | python3 scripts/convert-plugin.py --format opencode-v1 \ |
| 229 | --config ./opencode-mcp.json --name migrated-tools --output ./migrated-opencode |
| 230 | ``` |
| 231 | |
| 232 | 如果数据采用 `mcp.servers.<name>` 结构,请选择 `--format opencode-v2`; |
| 233 | 该格式的服务器开关是 `disabled`,不是 `enabled`。根据数据选择格式, |
| 234 | 不要依据文件名或上游分支名推断版本。两种格式的远程服务器都要求明确设置 `oauth: false`。 |
| 235 | 远程 MCP 输出**仅使用 Streamable HTTP**,不会复现 OpenCode 回退到旧版 SSE 的行为。 |
| 236 | 对于仅支持 SSE 的端点,请手工编写包含 `type: "sse"` 的原生 `mcp.json`, |
| 237 | 并使用相同的审查流程。 |
| 238 | |
| 239 | 选定 MCP 服务器时,配置中若包含 `tools`、`permission`/`permissions`、 |
| 240 | `agent`/`agents`、旧版 `mode` 或 `default_agent`,转换将被拒绝。 |
| 241 | 这些设置可能在服务器开关之外进一步限制工具访问。请先在 Codewhale 中手工保留 |
| 242 | 这些限制,再提供只含 MCP 声明的输入;直接删除这些设置可能扩大访问权限。 |
| 243 | |
| 244 | 转换器不接受 JSONC 注释或末尾多余逗号;请提供只含待移植声明的纯 JSON 副本。 |
| 245 | |
| 246 | ### DeepSeek Harness(DSH) |
| 247 | |
| 248 | 将以下静态 Cordis 条目列表保存为 `dsh-mcp.yml`: |
| 249 | |
| 250 | ```yaml |
| 251 | - name: '@deepseek-ai/dsh-mcp-client' |
| 252 | disabled: true |
| 253 | config: |
| 254 | serverName: docs |
| 255 | transport: streamable-http |
| 256 | url: https://example.invalid/mcp |
| 257 | ``` |
| 258 | |
| 259 | ```sh |
| 260 | python3 scripts/convert-plugin.py --format dsh \ |
| 261 | --config ./dsh-mcp.yml --name migrated-dsh --output ./migrated-dsh |
| 262 | ``` |
| 263 | |
| 264 | DSH 输入也可以使用 JSON,但必须是普通条目列表,不能是完整 profile 或 |
| 265 | patch 组合。每个条目的 `name` 都必须为 `@deepseek-ai/dsh-mcp-client`。 |
| 266 | |
| 267 | 真正的 DeepSeek Harness 插件包(即 `package.json` 中声明了 `dsh.bundle.patch` 的 |
| 268 | npm 包)由 Codewhale 原生导入,而不是通过此脚本(`--bundle` 已停用): |
| 269 | |
| 270 | ```text |
| 271 | /plugin import dsh ./node_modules/@demo/tools-dsh |
| 272 | /plugin import dsh approve <package-dir> <content-hash> |
| 273 | ``` |
| 274 | |
| 275 | 第一条命令把该包转换到临时目录,并显示哪些内容会被转换、哪些会被跳过、插件包将 |
| 276 | 申请的网络主机和本地进程,以及它的内容哈希;此时不会安装任何东西。`approve` 通过 |
| 277 | 普通的、经过审查的安装器,精确安装刚才转换出的那个插件包:安装后处于未启用、未信任 |
| 278 | 状态;如果包在审查之后被修改,则不会安装任何内容。把包目录当作普通的 |
| 279 | `/plugin install <dir>` 来安装,也会走同一个导入器。`/plugin update <name>` 会重新转换 |
| 280 | 已记录的包;输出发生变化时会替换原插件包,并使其信任记录失效。Runtime API 通过 |
| 281 | `POST /v1/apps/plugins/import/dsh/preview` 提供同样的审查,其返回的 `install_source` |
| 282 | 和 `content_hash` 用于 `POST /v1/apps/plugins/install`。 |
| 283 | |
| 284 | `dsh.bundle.patch` 可以指定单个 patch 文件,也可以指定有序的文件列表。导入器只读取 |
| 285 | 包内、非链接的文件(合计最多 64 个文件、1 MiB 的 patch 数据),然后在一个空 profile |
| 286 | 上应用 `insert` 和带键的覆盖。与上游的 `applyEntryPatches` 一致,覆盖会替换整个字段: |
| 287 | `config` **不会**做深度合并。这不是完整的 profile 解析器:其他插件包、用户叠加层和 |
| 288 | 部署配置都不在其中。普通 YAML 标量遵循 YAML 1.2 core schema,与 DSH 自己的解析器一致。 |
| 289 | |
| 290 | 被禁用的组会把禁用状态传递给其后代。被禁用的 MCP 声明保持禁用。被禁用的 skill 会被 |
| 291 | 省略并给出明确的回执,因为原生 skill 格式没有禁用状态。组或可移植条目上带条件的/ |
| 292 | 非布尔的 `disabled` 值会使导入被拒绝,而不会假定它已启用。不受支持的条目策略/依赖字段 |
| 293 | (包括 `inject`、`intercept` 和 `isolate`)同样会使包含它们的条目或组的导入被拒绝。 |
| 294 | 请在手工移植时保留它们的激活规则和权限规则。 |
| 295 | |
| 296 | 外部运行时插件和 `dsh.client` 界面代码不会被执行或转换。其他无法表示的组件会记录在 |
| 297 | 插件包的 `CONVERSION.md` 和结构化的 `CONVERSION.json` 中,其中包含源包名/版本、清单和 |
| 298 | 有序各层的 SHA-256 哈希、转换器版本、逐条目的结果,以及需要手工移植的内容。未应用的 |
| 299 | patch 操作也会出现在结构化的手工移植列表中,并附带其来源层和从 1 开始的操作序号。 |
| 300 | |
| 301 | 能被转换(lowering)的 `!!js` 表达式只有 `process.execPath`(变成 `node`)和不含转义或插值的 |
| 302 | 简单带引号/模板字面量。环境变量表达式(包括带回退值的)永远不会按本机环境求值。 |
| 303 | 已打包的 Node MCP 只有在其相对入口(以及声明的相对工作目录)解析后位于包内时才会被 |
| 304 | 转换;包外的服务器会被跳过,宿主路径永远不会被复制。`@deepseek-ai/dsh-skill-filesystem` |
| 305 | 条目只有在其字面的 `customSkillDirs` 子目录位于包内时,才会贡献这些子目录。默认的用户 |
| 306 | 和项目 skill 根目录、文件监视器以及外部服务依赖都不会被导入。任意 DSH TypeScript |
| 307 | 插件的执行不在此静态导入器的兼容范围内。明确编写 Native 入口的插件可通过实验性的 |
| 308 | TypeScript 扩展宿主提供工具、用户命令、执行前提议、提示词片段和插件本地状态; |
| 309 | 宿主不会执行导入的 `dsh.bundle.patch` 组合。参见[扩展编写指南](./EXTENSIONS.md)。 |
| 310 | |
| 311 | ### 本地 Node MCP 服务器 |
| 312 | |
| 313 | 对于已打包的 Node MCP 服务器,使用 `--stdio-root SERVER=DIRECTORY` 明确选择 |
| 314 | 原进程的工作目录。转换器将整个目录复制到 `mcp/<server>`,并把原生服务器的 |
| 315 | 工作目录设为经审查的副本,保留相对入口导入和只读资源的目录布局。 |
| 316 | |
| 317 | ```json |
| 318 | { |
| 319 | "mcp": { |
| 320 | "localdocs": { |
| 321 | "type": "local", |
| 322 | "command": ["node", "server.mjs"], |
| 323 | "environment": {"API_TOKEN": "{env:LOCALDOCS_TOKEN}"}, |
| 324 | "enabled": false |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | ``` |
| 329 | |
| 330 | ```sh |
| 331 | python3 scripts/convert-plugin.py --format opencode-v1 \ |
| 332 | --config ./local-mcp.json --stdio-root localdocs=./packaged-localdocs \ |
| 333 | --name local-tools --output ./migrated-local |
| 334 | ``` |
| 335 | |
| 336 | 每个本地服务器都必须提供对应的 `--stdio-root`。OpenCode v2 使用 `mcp.servers` |
| 337 | 和 `disabled`。静态 DSH 条目使用 `transport: stdio`、`command: node`、 |
| 338 | `args: [server.mjs]`;DSH 的 `env` 必须省略或为空,其字面量和表达式不按 |
| 339 | OpenCode 环境引用解释。DSH/v2 的 `cwd` 只能省略、为空或为 `.`;选定目录明确 |
| 340 | 指定原进程的工作目录。只有远程 OpenCode 服务器需要 `oauth: false`。 |
| 341 | |
| 342 | 支持 `node` 加一个相对 `.mjs`、`.js` 或 `.cjs` 入口。原生启动适配器保留包的模块类型 |
| 343 | 和同级模块导入。TypeScript 需先编译为 JavaScript;转换器不会运行编译器。依赖和只读资源必须 |
| 344 | 预先打包在该目录内;转换时不运行包管理器、安装脚本、模块加载器或服务器。 |
| 345 | 符号链接/reparse point、硬链接文件、隐藏文件或目录(包括 `.gitignore`、 |
| 346 | `.env*`、`.npmrc` 和 `node_modules/.bin`)、常见凭据文件名及私钥容器会被拒绝。 |
| 347 | 请准备干净的打包目录;不会根据忽略规则静默遗漏文件。转换前检查每个选定文件 |
| 348 | 是否嵌入凭据。整个输出仍受 4,096 个文件 / 64 MiB 限制。 |
| 349 | |
| 350 | Shell 启动器、Node 标志、额外参数、其他解释器、环境变量字面值以及改变模块 |
| 351 | 加载方式的环境变量名均不支持。写入工作目录、依赖实时工作区或导入包外文件的 |
| 352 | 服务器需要手工移植。复制文件不等于静态验证 JavaScript 依赖闭包,也不提供 |
| 353 | 代码沙箱。本地 MCP 以宿主用户权限运行;远程端点的主机声明不会限制本地进程 |
| 354 | 的网络或文件访问。Codewhale 启动服务器前仍需完成原生安装、能力审查、哈希绑定 |
| 355 | 信任和启用流程。此功能是已打包 Node MCP 子集,不执行 DSH/Cordis 插件模块。 |
| 356 | |
| 357 | ### 审查转换结果 |
| 358 | |
| 359 | 两个示例都保留服务器的停用状态,并使用占位端点。准备连接时,先替换源文件中的 |
| 360 | 端点并修改启用开关,再重新转换。输出目录必须尚不存在,且其父目录已存在。 |
| 361 | 已有输出会被拒绝覆盖;输入被拒绝时不会留下输出插件包。 |
| 362 | |
| 363 | 添加 `--skill ./my-skill` 可选择包含 `SKILL.md` 的目录,也可以用 |
| 364 | `--skill ./my-skill.md` 选择单个文件;重复该选项可选择多个 skill。 |
| 365 | 仅转换 Skills 时可以省略 `--config`。Skills 的 frontmatter 必须包含 |
| 366 | `name` 和 `description`。`disable-model-invocation: true` 会转换为原生的 |
| 367 | `invocation: explicit-only`。说明性字段 `license`、`compatibility` 和 |
| 368 | `metadata` 会保存在伴随数据文件 `SOURCE_SKILL_METADATA.json` 中。 |
| 369 | 所选 skill 目录中的其他文件会作为数据复制;加载 skill 前需审查这些文件及其指令。 |
| 370 | |
| 371 | 只有完全符合 `{env:MCP_TOKEN}` 形式的 OpenCode 请求头引用会转换为原生 |
| 372 | `env_headers`;转换器不会读取变量值。字面量请求头、DSH 请求头表达式以及 |
| 373 | URL 中的文件或环境变量替换都会被拒绝。配置的超时值必须是以毫秒表示的整秒数, |
| 374 | 范围为 `1000` 到 `3600000`。未指定的超时使用 Codewhale 的默认值。 |
| 375 | |
| 376 | 外部运行时的可执行插件和 hooks、其他 stdio 启动方式、自动 OAuth、配置中的 |
| 377 | JavaScript、YAML 别名或标签、 |
| 378 | `__jsExpr`,以及不支持的 skill 运行时字段(包括 `user-invocable: false`) |
| 379 | 需要手工移植。转换不会复现其他客户端的运行时,也不会绕过 Codewhale 的凭据 |
| 380 | 和沙箱规则。 |
| 381 | |
| 382 | 阅读生成的 `CONVERSION.md`、`CONVERSION.json`(针对插件包)、`plugin.json`、`mcp.json`(如有)以及 |
| 383 | 所有选定的 skill 和 MCP 源文件。然后使用 `/plugin install ./migrated-opencode`(或 DSH 输出路径)、 |
| 384 | `/plugin validate <name>`,并按前文相同的哈希绑定流程审查、信任和启用。 |
| 385 | 转换本身既不证明连接可用,也不证明运行时兼容;输出尚未安装、信任或启用。 |
| 386 | |
| 387 | 源码核查日期:2026-09-08。依据 OpenCode `d6855b6b47` 的 |
| 388 | [v1 MCP 文档](https://github.com/anomalyco/opencode/blob/d6855b6b47a8433462ac6aeeba882ccf734cb7f1/packages/web/src/content/docs/mcp-servers.mdx) |
| 389 | 和 [v2 MCP schema](https://github.com/anomalyco/opencode/blob/d6855b6b47a8433462ac6aeeba882ccf734cb7f1/packages/core/src/config/mcp.ts), |
| 390 | 以及 DSH `c389f96bf3` 的 [MCP 客户端参考](https://github.com/deepseek-ai/deepseek-harness/blob/c389f96bf3a9b6807cb71ed6bdad5849be0df6d8/packages/mcp/mcp-client/README.md)。 |
| 391 | Bundle patch 的语义已对照 DSH |
| 392 | [`00102833df`](https://github.com/deepseek-ai/deepseek-harness/tree/00102833dfaee1da9f48a3a8eae9d34005a75218) |
| 393 | (`0.1.7-alpha.2`)重新核对;`scripts/fixtures/dsh-web-app` 保留其真实的五文件包, |
| 394 | 仅作为解析器测试数据,不是可导入的原生插件。CI 使用 Python/PyYAML 和合成的 Node |
| 395 | 夹具运行离线转换器测试语料。上游支持的功能多于这个有意限定范围的转换器。 |
| 396 | |
| 397 | ## 社区背景 |
| 398 | |
| 399 | 本指南回应了 [giancarlocp 在讨论 #5827 中提出的插件编写指南与 OpenCode |
| 400 | 转换需求](https://github.com/codewhale-hq/Codewhale/discussions/5827)。 |
| 401 | 简体中文版本遵循 [SparkofSpike 在 issue #5482 中提出的中文文档工作方向](https://github.com/codewhale-hq/Codewhale/issues/5482)。 |
| 402 |