2 · 核心概念

这一章只讲六个必须理解的概念:插件、装配 id 与访问路径、三处逐字一致、lib/ 构建产物、 profile 与 patch 覆盖层、包根形态。理解完,你就能看懂任何一份 cordis.patch.yml。

插件:DSH 的部署与运行单元#

DSH 是全插件(all-plugin)架构:你不改宿主,只写插件、声明要用哪些服务,然后在 apply() 里消费它们。 一个插件就是一行 Cordis patch——装进宿主后,宿主启动时会逐行加载。

yamlcordis.patch.yml · 最小一行
- insert:
    - id: kth-bus-my-plugin
      name: kth-bus-my-plugin

插件有两种形态,先想清楚你要哪种:

形态你要提供适用
Host-onlypackage.json + cordis.patch.yml + index.js(+ 构建脚本)后台服务、HTTP API、工具、调用 LLM/Agent、定时任务。KTH 业务插件多是这种,页面由服务端返回自包含 HTML。
Host + Web Client再加客户端入口(dsh.client + ./client)要往宿主 Web UI 里加页面 / 面板 / 节点。

装配 id ≠ 访问路径#

这是最容易踩的坑:patch 行的 id 只是装配标识,网页 URL 由插件自己注册。

装配 id访问路径
来自插件库 cordis.patch.yml 的行 id插件在 ctx.webServer.register({ path }) 里自注册
例子kth-bus-yyjz-agents/bus-yyjz-agents/
用途装配、依赖、装卸的标识浏览器里访问的 URL
关系通常等于包名与 id 无关,可任意取名
别用 id 拼 URL

访问的是 http://localhost:3080/bus-yyjz-agents/,不是 /kth-bus-yyjz-agents/。 要确认某个插件注册了什么前缀,看它的 src/index.ts 里的 path。

三处逐字一致#

加载器要求「注册 id 等于包名」,所以下面三处必须逐字一致(第三处仅 Host + Web Client 形态需要):

位置值示例说明
package.json 的 namekth-bus-my-plugin包名
cordis.patch.yml 的行 namekth-bus-my-plugin装配引用
前端 window.__ModuleLoader__.load({ id })kth-bus-my-plugin客户端注册 id

构建产物 lib/:宿主加载它,不加载 src/#

你写的是 src/,宿主读的是 lib/。所以改完源码必须重新构建,并重启宿主, 否则你会对着旧行为排查半天。各插件构建通常用 esbuild 把 TS 打成自包含 ESM。

bash在插件包根内构建
# 在插件自己的包根里(不是库根、不是别处)
npm install
npm run build          # 产出 lib/index.js
node --check lib/index.js
kth-bus-* 的惯例

打成自包含产物(不 import 宿主包 @deepseek-ai/dsh-* / @deepseek-ai/cordis),运行时用 ctx.get() 取服务、自持最小类型;lib/ 不入库。

profile 与 patch 覆盖层#

  • profile 是宿主的组合清单(客户基座里装了哪些插件)。
  • --patch 覆盖层只做 insert:把本地插件按序追加进来,不改动 profile,也不卸载官方插件。
  • 行 name 相对本文件解析:写 ../../Infrastructure/... 这类相对路径,换机器不用改绝对路径。
  • 生效时机:bundle 变更下次启动生效——改完 patch 要重启宿主。
yamlkth-deploys/local-dev/dev-plugins.patch.yml(节选)
- insert:
    - id: kth-infra-configmanagement
      name: ../../Infrastructure/infra-configmanagement/content/lib/index.js
    - id: infra-database-postgresql
      name: ../../Infrastructure/infra-database-postgresql/content/lib/index.js
    - id: kth-bus-yyjz-agents
      name: ../../Infrastructure/bus-yyjz-agents/lib/index.js

启动时用 --patch 指定覆盖层(它是启动器参数,必须写在 --no-open 之前):

powershellWindows / PowerShell
$env:DSH_HOME = "$HOME\kth\.dsh-home"
cd $HOME\kth\kth
pnpm dsh web --patch "$HOME\kth\kth-deploys\local-dev\dev-plugins.patch.yml" --no-open
bashLinux / macOS
export DSH_HOME="$HOME/kth/.dsh-home"
cd ~/kth/kth
pnpm dsh web --patch ~/kth/kth-deploys/local-dev/dev-plugins.patch.yml --no-open
校验组合树

用 dsh --profile <name> --dump-config 看最终组合,确认你的行生效、且没被更高优先级层覆盖。

包根两种形态与菜单内嵌#

加载器找包根时:优先 <插件>/content/,否则用 <插件>/ 本身——两种形态都要兼容。

text两种包根
bus-image-detection/            bus-yyjz-agents/
├── content/          ← 优先  ├── package.json      ← 直接用包根
│   ├── package.json          ├── src/index.ts
│   └── lib/index.js          └── lib/index.js
└── (其余源码)                (无 content/ 目录)

package.json 里的 dsh.embed 是给 kth-base-permission 用的菜单项, 声明「这个页面可以作为菜单内嵌」。内嵌时宿主用 ?embed=1 隐藏自带顶栏。

jsonpackage.json(片段)
"dsh": {
  "bundle": { "patch": "./cordis.patch.yml" },
  "embed": [{ "label": "我的页面", "path": "/my-plugin/" }]
}

一次请求 → 插件 → DSH seam#

text请求进入后发生什么
GET /bus-yyjz-agents/api/xxx
        │
        ▼
DSH 宿主 · webServer seam
        │  按前缀路由匹配
        ▼
kth-bus-yyjz-agents  插件(自注册前缀 /bus-yyjz-agents/)
        │  apply() 里按需组合上下文
        ├──▶ ctx.auth          会话校验(登录守卫)
        ├──▶ ctx.infraConfig   读台账(台账 > 代码缺省)
        ├──▶ ctx.agents        跑智能体 / 会话
        ├──▶ ctx.llm           流式模型调用
        ├──▶ ctx.tools         注册 / 调用工具
        └──▶ ctx.skills        技能(<DSH_HOME>/skills)
        │
        ▼
返回 JSON,或自包含 HTML 页面
下一步

概念齐了,去 3 · 环境准备 装好 Node / pnpm / Git,然后 4 · 快速开始 动手跑通。