第 4 卷
看 b-chat 这行实测输出:
复制代码 1[chat] 收到:[b-provider/b-model] 回复:你好(我看到 shell 配置:{"model":"intercept-m
chat() 返回的还是 [b-provider/b-model],实例纹丝未动;但解析配置时,model 被 intercept 的值合并覆盖,provider 等没提到的字段原样保留。租户 B 两个都用:
isolate 给它私有 shell,intercept 让它看到被改写的 model。

到这里,cordis.yml 里绝大部分字段你都见过了。给你一份“字典”,忘了随时回来查:
id条目的稳定标识,组内子条目的全名会带祖先前缀tenant-a、tenant-a:a
./plugins/logger.ts、@ name插件模块:相对路径或包名 plugin-group
config传给插件的配置对象(先过 schema 安检)logger 的 level、shell
标记这是一个“组”条目,config 里是子条目数组;必须同时写 name: '@deepseek-ai/cordis-plugin-
group: truetenant-a / tenant-b group'——“组”不是语法糖,它本身就是一个真实插
件
disabled保留条目不挂载(祖先连坐)spare
给同名服务换 symbol 键,各住一间:字符串 = 标同 isolateshell: tenant-a、shell:
一 label 的共享一份,true = 条目私有
表外其实还有一个 inject 字段:它可在条目级追加插件依赖的服务(插件代码里 export const inject 的配置版),没就绪就停在 PENDING。这一讲的最终版配置没用到它,所
以没列进表里——用法与第二讲的代码版一致。
热更新(HMR)
先会用:HMR 怎么配、怎么跑
树搭好了,配置也讲完了。开发或线上部署时最烦的就是改一行配置 / 代码,重启整个进程。
HMR(Hot Module Replacement)就是干这个的:文件一变,只有相关的那块被替换,进程不重启。
hmr 插件要求进程带 --expose-internals 启动,所以我们构建了单独的一份 cordishmr.yml,业务部分用嵌套 include 复用 cordis.yml——一行内容都不用重复:
| 3 | name:'@deepseek-ai/cordis-plugin-timer'* |
|---|---|
| 4 | -id: hmr #②HMR插件自己 |
| 5 | name:'@deepseek-ai/cordis-plugin-hmr'* |
| 6 | config: |
| 7 | root: # 监听哪些目录(省略也行,默认就是[' |
| 8 | -'.' |
| 9 | -id: app #③嵌套include:直接复用cordis.yml |
| 10 | name:'@deepseek-ai/cordis-plugin-include' # 业务配置一行都不用重复 |
| 11 | config: |
| 12 | path:'./cordis.yml' |
注意最上面那个 timer:它不是可选的装饰,而是必须挂的——HMR 要用 timer 服务做防抖,少了它 HMR 就会一直卡在 PENDING,而且一声不响:树照挂、其他插件照跑,只是热
更新永远不来。
于是入口和 main.ts 结构一致,还是建根、挂 Loader、挂 include 那三步,只有两处不同:config.path 换成 './cordis-hmr.yml',末尾加一行 setInterval(() =>
{}, 1000) 保活(HMR 要盯着文件,进程退出就没人监视了)。
启动命令如下(它连 Node 版本一起钉住了):
复制代码 1npx node@24 --expose-internals --import tsx src/ch04/hmr-main.ts
为什么强调 Node 版本?因为 HMR 要动 Node 内部的模块加载 API,各版本签名不同,版本不对会报错。第一次跑直接用上面这条命令就行。
启动后树照常挂载,然后就可以动手了。我们改两处试试:
- 改配置:把 cordis.yml 里 logger 的 level 从 !!js process.env.LOG_LEVEL ??