Ctrl K
ComunidadGuías…

doc/plugin-development.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<!-- Generated from doc/plugin-development.md.tmpl by scripts/version.mjs; edit the template. -->

# 插件作者接入

作者在自己的仓库开发和打包,部署者只取得标准发布目录。先选适合自己的入口:

| 项目情况 | 做法 |
| --- | --- |
| 新建 DSH 插件 | 从本页起步包开始,先用原名跑通,再改业务 |
| 已有 Node 项目 | 调整为官方 Cordis 插件入口,提供 Bundle 和构建产物;不是只添加几个字段就能运行 |
| 非 Node 服务 | 服务继续独立部署,由一个 DSH 插件调用接口;框架不接管 Java/其他进程 |

直接打包支持独立 pnpm 单包。npm/yarn/monorepo 不承诺同样的一步流程;部署端只认合规发布物,不扫描作者源码。[部署者指南](first-deployment.md)与[配置规范](plugin-configuration.md)分别说明运行和声明。

作者主路径只需要 `list`、`check`、`pack` 和 `verify-package`。其中 `pack` 会冻结安装并执行 build/check,这是交付前最后一道检查;站点部署、恢复和迁移由部署者按对应文档处理。

## 独立仓库开发

### 1. 取得并安装工具

<!-- excerpt:author-tools -->
需要 Node.js `^22.19.0 || >=24`、pnpm `11.19.0` 和系统 tar。从同一框架 Release 取得 `dsh-plugin-manager-starters-0.22.0.zip`、`plugin-manager-0.22.0.tgz`;起步包的鉴权目录已带同版 kit;仅单独复制仓库示例或升级 kit 时另取 `plugin-kit-0.22.0.tgz`。核对随发行提供的 SHA-256,不假设这些包已发布到 npm registry。

起步 zip 内有 standalone-plugin、standalone-kit;选一个目录复制为自己的作者项目,不复制 node_modules、dist 或 .local。在作者项目以外创建独立工具目录 dsh-tools,在该工具目录安装实际 manager 归档:

```sh
pnpm init
pnpm add --ignore-workspace /absolute/path/plugin-manager-0.22.0.tgz
pnpm exec dsh-plugin-manager --version
```

将占位路径替换为实际绝对路径,含空格时加引号。以后 pnpm exec dsh-plugin-manager 都在这个工具目录执行,--root 明确指向作者项目。manager 不加入业务运行依赖;工具目录和作者项目各自保存锁文件。
<!-- /excerpt:author-tools -->

附件见 [GitHub Releases](https://github.com/PelyDeng/dsh-plugin-manager/releases)。普通作者不用克隆框架;需要维护框架源码站点时再阅读[源码部署](../deploy/README.md#服务器源码发版)。

### 2. 先按原名跑通

Release 起步包的两个示例都在作者根执行 `pnpm install --ignore-workspace`。仅从仓库单独复制 standalone-kit 时,先把对应 kit tgz 保存为自己项目的 vendor/plugin-kit.tgz,再在作者根执行:

```sh
pnpm add --ignore-workspace --save-dev ./vendor/plugin-kit.tgz
```

vendor 是作者选择保存的公开构建输入,不能放真实配置;保留该归档和作者 pnpm-lock.yaml 以便再次构建。kit 内嵌到产物,运行包不能留下作者本地 file/link/workspace 路径。官方宿主实现仍由运行环境提供。

### 3. 构建、检查并交付

<!-- excerpt:author-pack -->
在工具目录执行,将作者项目换成实际绝对路径:

```sh
pnpm exec dsh-plugin-manager list --root /absolute/path/my-plugin --package .
pnpm exec dsh-plugin-manager pack --root /absolute/path/my-plugin --package . --output .local/artifacts/release/v1
pnpm exec dsh-plugin-manager verify-release --release .local/artifacts/release/v1
```

list 只读声明,不要求锁文件。pack 只做构建、打包与内容寻址(归档按实际字节摘要命名),不再顺带做检查或校验;输出必须是新目录或空目录,路径相对作者 root,再次发布用新目录 v2。自检是三条独立命令:`check`(先 build 再跑类型检查等,日常开发用它)、`verify-package --root <包根> --package . --archive <tgz>`(归档与本次构建源码字节一致)、`verify-release --release <发布目录>`(整个交付目录的清单、摘要、包结构与元数据合规,可进 CI)。pack 成功后 CLI 只说明生成了什么,不说"已验证"。

交付整个输出目录,其中有 manifest.json 和所有摘要命名 tgz。部署者把目录放到 incoming/my-plugin 后执行框架 build,不手写清单。不使用 prepare/prepack/postpack 重复构建。运行依赖不得指向作者机器或 workspace;pack 成功不是宿主、登录、模型或业务验收成功。交付时以整个目录为单位,不单独抽走 tgz。
<!-- /excerpt:author-pack -->

按[首次部署](first-deployment.md)用真实请求跑通。无 kit 示例请求 `/independent-example/ready`;鉴权示例用构建产出的内置 auth、登录与授权后请求 `/independent-access-example/identity`。有了第一次成功后再改名称和业务。

### 4. 改名与实现业务

| 修改项 | 位置 | 需要一致的关系 |
| --- | --- | --- |
| npm 包名与作者版本 | package.json | 归档身份;独立于框架版本 |
| 插件 ID | deepseekPlugin.id、src 导出的 name/注册 ID | 用于部署选集和配置目录,不能与已有插件重复 |
| Bundle 插件名和 entry ID | cordis.patch.yml | 导入实际 npm 包;configuration.entryId 指向可配置条目 |
| 页面与探针路由 | src、entryPath/healthPath | 声明对应真实地址,不要求它们都与包名相同 |
| 权限 | permissions、注册信息、访问检查 | 使用同一应用权限;声明本身不保护路由 |

最低声明为 package.json 的 name/version/main/files、dsh.bundle.patch、deepseekPlugin schemaVersion=3/id、scripts.build/check 和包内 README。完整字段只在[配置规范](plugin-configuration.md)维护。需要业务参数时用 config 或声明 runtimeConfig;缺项错误包含插件 ID 与字段名,不输出凭据。不要复制框架登录、密码或部署实现。

后续开发循环:改代码并运行业务测试 → pack 到新的发布目录 → 整体替换 incoming 对应目录 → build → 再请求验证。多个应用保留完整选集,更新与配置错误恢复见部署指南。当前不提供外部 development/link/HMR。

### 5. 给工具打分类标签

工具用 kit 的 `createPluginTools` 登记,第三个参数是**分类标签**,由插件自己填写:

```js
const tools = createPluginTools(ctx, { permission: 'my-plugin:access', authorize })
tools.register(definition, '查询订单', '订单管理')   // 第三项就是分类
```

分类有两个用途。一是认证页面按标签分组展示,让使用者看清一个插件提供什么。二是**限制某个
Agent 只能调用自己标签下的工具** —— 宿主用 `toolsForCategory(全部条目, 本分类)` 算出「本分类
+ 通用工具」的名单,再在**该 Agent 的 agent 作用域**里用 `tools.restrict({ allow })` 应用:

```js
// 必须在 agent 的 setup(agent.ctx)里做。插件级限制会波及所有 Agent,宿主会直接拒绝:
// tools.restrict() requires a scoped context (agent.ctx)
agentCtx.tools.restrict({ allow: toolsForCategory(allTools, '订单管理') })
```

约定好的公共工具集使用固定标签 `通用工具`(kit 导出的 `UNIVERSAL_TOOL_CATEGORY`),任何
Agent 都能调用。四条容易写错的边界:

- **通用集必须并进来**,否则 Agent 连公共工具都调不了;
- **未分类的工具不自动放行**。不传分类的旧插件照常工作,但不参与这套限制 —— 顺手放行会让
  限制形同虚设;
- **`allow` 只认宿主全局注册的工具名**。把本 Agent 作用域里刚注册的局部工具写进 allow 会以
  `names unknown global tool` 失败;局部工具本来就只对该 Agent 可见,不必列入;
- **不要拿空数组去 restrict**。`allow: []` 会遮蔽全部工具(包括该 Agent 自己的),目录读不到
  时宁可不施加限制。

`category` 是可选字段,不填不影响工具可用。

## 内部工作区开发

### 1. 安装依赖并扫描插件

在本仓库根执行,内部流程不传 `--package`:

```sh
pnpm install --frozen-lockfile
pnpm list:plugins
```

**预期**:继续扫描 `plugins/builtin/*`。新增插件放在该目录并按[配置规范](plugin-configuration.md)声明;使用 `--plugins` 选择需要处理的插件。源码内置插件就是 auth 和 example,选集留空即采用全部候选;`DSH_PLUGINS` 显式列出时才收窄。

### 2. 选择日常检查或直接打包

| 目的 | 在本仓库根执行 |
| --- | --- |
| 日常构建 | `pnpm build` |
| 必要检查 | `pnpm check`,单插件可用 `pnpm check --plugins example` |
| 完整开发回归 | `pnpm test`,先构建再运行测试;CI 单独执行测试步骤 |
| 构建并交付 | `pnpm package --plugins "auth,example" --output .local/artifacts/release/plugins` |

按当前改动选择上表中的命令,不必每次全部执行。只交付时直接 package,不需要先重复 build/check。普通构建和测试不要求宿主子模块、模型密钥或 Docker;内部清单 1 保留 development/link,外部单包与组合清单 2 只支持 release。

### 页面怎么回归

<!-- excerpt:author-page-test -->
插件的页面代码是浏览器原生模块,没有 jsdom 能覆盖的执行环境,所以布局改动最容易只能靠人眼。仓库提供的做法是**真的把页面跑起来量一遍**:`scripts/web-page-probe.mjs` 起静态服务、按桩文件顶掉接口、用无头 Chromium 在多个视口宽度下执行你写的探针表达式,再把结果打回来。

```bash
node scripts/web-page-probe.mjs --root plugins/builtin/dsh-auth --prefix /auth \
  --stub plugins/builtin/dsh-auth/tests/page-stub.json --probe plugins/builtin/dsh-auth/tests/page-probe.js \
  --widths 1150,860,640
```

探针就是一段返回可序列化值的表达式,能直接读 DOM(样例量的是每行卡片数、同一行卡片头部与页脚的位置差、是否横向溢出、说明渲染了几行)。`plugins/builtin/dsh-auth/tests/page-layout.test.mjs` 把这些量变成断言,`pnpm test` 里就会跑:宽视口必须出现三张一行、窄视口回落单列、任何宽度都不许出现错位或横向溢出。

页面 HTML 与构建产物不在同一目录时:用 `--dir <目录>` 换掉页面目录(默认 `<root>/web`),用 `--mount <路径=目录>` 把产物目录挂到宿主约定的资源前缀下;HTML 里有待宿主替换的占位配置时,用 `--replace <词=文件>` 在返回前换成该文件内容。探针与桩文件放在插件的 `tests/` 下随源码维护,不需要额外依赖。加 `--json` 只输出结果,便于脚本消费。

**断言要守住两条底线**:产物缺失属于构建问题,应当直接失败并给出该跑的命令;只有机器上确实没有可用 Chromium 时才允许跳过(`CHROME_PATH`/`--browser` 指定,`--require-browser` 把跳过变成失败),而且跳过在测试里必须记成真正的 SKIP,不能算通过。
<!-- /excerpt:author-page-test -->

打包会并行构建与打包多个插件:插件之间没有依赖,同时最多 4 个(`pnpm package --concurrency <n>` 可调整,1 表示逐个进行)。依赖安装与共享依赖准备先单线做完,再进入并行阶段;日志按插件 ID 加前缀以便分辨,交付清单里的插件顺序仍按选集,与并发无关。

站点内置插件固定全量构建,不再按插件点名重建;部署选集、构建输入与重试方式见[部署说明](../deploy/README.md#服务器源码发版)。日常 `pnpm package --plugins c` 只生成 c 的交付清单,不会自动补入其他插件。

构建输入来自插件自身目录、`dependencies`、`devDependencies`、`optionalDependencies`、`peerDependencies` 里声明的本地依赖(`workspace:` 按包名解析,含间接依赖),加上仓库级共享输入(`package.json`、`pnpm-lock.yaml`、`pnpm-workspace.yaml`、`.npmrc`、`.gitattributes`)。读取其他插件源码也属于构建依赖,应通过本地包名和标准 `workspace:` 声明;example 读取 auth 源码生成索引,因此把 auth 声明为开发依赖。

内置构建固定使用框架发行方的完整公开构建视图(`packages`、`plugins/builtin`、`scripts`、`deploy`、`integrations`、`examples`、`doc`、`.github` 以及公开根构建文件与模板),视图内的材料都是内置构建的输入;每次 build 都为选集里的每个插件重新构建,不按旧成功记录逐插件复用,因此不需要逐插件声明构建输入。旧字段 `deepseekPlugin.buildInputs` 已退役,声明它会被当场拒绝;需要读取公开视图之外的东西应改为标准包依赖声明。

本地构建归档可声明为 `file:vendor/library-0.1.0.tgz`,但必须位于声明它的插件自身目录内,是已纳入 Git 的常规 `.tgz` / `.tar.gz` 文件;归档及父目录不能是符号链接,跨目录、目录形式、未跟踪或仅在忽略目录中的归档,以及 `link:` 依赖都不被接受。这不改变最终发布包不能携带 `file:` 运行依赖的约束:本地归档用于构建,所需代码应内嵌到插件产物。

插件构建由显式 build/check/pack 流程执行,不得用依赖安装钩子触发插件构建或修改产物。

### 3. 运行并验证

首次运行按[配置并启动](getting-started.md#3-配置并启动)操作。站点 build 仅支持 release;底层 CLI 的 development 与管理参数见[字段参考](framework-configuration.md#独立-cli-字段参考)。示例默认要求登录,候选选入 auth、example 并配置公开 origin。安装现成清单默认选择其中全部插件,也可通过 `--plugins` 限定。

## 复制完整问答应用到独立仓库

复制 `plugins/builtin/dsh-example` 中的源码、scripts、web、knowledge、examples、skills、Bundle、README/LICENSE、package.json、tsconfig 与 tsdown 配置;不复制 node_modules、dist、数据库和 .local。选择一个未加入原框架 workspace 的新包根。问答视觉与交互遵循随包 [聊天风格 skill](../plugins/builtin/dsh-example/skills/dsh-chat-style/SKILL.md),包括折叠思考预览、流式更新和回答工具栏。

1. 在作者 package.json 删除 `@dsh-plugin-manager/plugin-kit` 的 `workspace:*` 开发依赖,再在作者根执行 `pnpm add --ignore-workspace --save-dev <kit-tgz绝对路径>`。同时删除只用于声明框架源码索引输入的 `dsh-auth` 开发依赖;索引读取第 5 步显式提供的框架源码。保留 tsdown 内嵌 kit,宿主依赖保持 peer。
2. 删除 scripts.clean 的原仓库相对入口,或换成只清理本包构建目录的实现。不要把数据目录加入清理命令。
3. `tests/config-examples.test.mjs` 含框架管理器集成检查,`tests/host-smoke.mjs` 使用框架相对宿主和归档路径;这两份留在框架,不复制到独立应用测试。其余 chat/history/knowledge 测试和 fixture 可作为应用自己的回归基础。
4. 修改包名、ID、Bundle、页面/探针、权限、配置 entryId、会话前缀/正则、提示词段名、知识、页面文案和测试;仅验证原 example 独立构建时可先保留名称,但不能与原包在同一候选中重复安装。
5. FAQ 读取包内 knowledge;源码问答索引在构建时生成。保留框架答疑用途时,把 build 中 `scripts/build-reference.mjs --root ../..` 的 root 改为明确的公共框架源码根路径;只在作者构建机需要该源码。部署时索引随 tgz 携带,不依赖作者目录。改成其他业务应替换 src/knowledge.ts 中职责、两份知识及源码检索能力,不能只改 config.systemPrompt;它仅为补充。
6. 在作者根执行 `pnpm build`、`pnpm check` 和 `pnpm test`,保存 pnpm-lock.yaml。在工具目录执行 `pnpm exec dsh-plugin-manager pack --root <作者包根> --package . --output <新发布目录>`。只交付无需事先重复 build/check。

部署者只取得整个发布目录及说明。确认 tgz 包含知识、页面、配置模板和入口;作者源码目录不参与 release。kit 更新需每个使用它的应用更新内嵌版本后重新交付,不能只升级管理器。

复制时保留[自动标题](conversation-management.md#自动标题)的全生命周期监听、owner 检查和手动标题保护;前端复用有界历史刷新。宿主已经提炼首句,业务插件不再另发标题请求。沿用历史数据库时核对 schema 4 的升级与回退边界。

## 接入与交付约定

作者按本节交付,站点只按完整发布目录安装,自检只在交付侧执行:

| 约定 | 内容 |
| --- | --- |
| 完整发布目录 | manifest.json 加全部摘要命名 tgz;一次整体替换 incoming/<应用目录>,不单独抽走 tgz、不手写清单 |
| 内容寻址命名 | 归档名为 `<id>-<sha256>.tgz`,摘要是归档实际字节;更新后换新目录(v1 → v2),同 ID 新版整体替换旧目录 |
| 可移植依赖 | 运行依赖不指向作者机器、workspace 路径或 file:/link:;本地构建归档只用于构建,所需代码内嵌进产物 |
| 入口与声明一致 | id、包名、版本、entryPath、healthPath 与归档一致;部署读路径从归档读实际包名,清单写错会被拒绝 |
| 不得包含私密配置 | 归档不含 env.conf、.env、凭据、token、客户数据;runtimeConfig 只声明变量与模板 |
| 配置归属 | 业务参数用 configuration 或 runtimeConfig;用户配置与部署数据不属于包资源 |
| 数据兼容性由作者说明 | 同包业务配置修正的兼容性由作者在发布说明中写清;管理器不代判跨包兼容 |
| 自检只在交付侧执行 | check / verify-package / verify-release 由作者与 CI 执行;站点 build 不重跑这些门禁,坏包在安装或验证时如实报错并给出独立检查命令 |

作者随归档提供公开配置模板、包内 README、已验证宿主版本、就绪地址和一次业务验证方法。manager 可通过 `compose-release --verification-report` 将最终归档的测试记录附入新发布清单,详细字段与命令见[发布物验证记录](../packages/plugin-manager/VERIFICATION.md)。pack 的构建不等于宿主或模型测试。不要在归档中加入真实凭据或客户数据。

使用说明应提供就绪地址、普通账号操作步骤和所需授权。销售接口、数据范围、Agent 工具和图表属于应用代码,kit 的可信身份不能替代业务数据授权。

交付后的配置、启动和更新参考[产物部署指南](first-deployment.md);需要自行组合清单或直接管理宿主时使用[手工 CLI 指南](../packages/plugin-manager/DELIVERY.md)。