DSH 插件开发操作
August 18, 2026 · View on GitHub
本文沉淀自「Trellis × DSH Dashboard」插件的完整开发过程(动态原型 + 发布包)。 官方对照:你的第一个插件、 服务与依赖、 事件系统、 打包与安装。
1. 开发全流程
DSH 动态插件经 Cordis 运行时加载,能力先查证、再定义、后激活:
1. cordis_inspect_list 发现当前 Host/Client 的 Inspect Provider(Service/Event/Builtin/Slot/Theme/Tool)
2. cordis_inspect_query 查询精确契约(方法签名、事件载荷、槽位协议、主题 token)—— 写码前必查
3. cordis_define 定义插件(host/client 两半代码,纯 JS)→ 返回 pluginId / packageId
4. cordis_run 激活(首次 run;换版本 update;回滚 run 旧包)→ 可能 awaiting-approval
5. cordis_inspect_self 读运行诊断 / 源码 / 版本指针
6. cordis_stop / undefine 暂停 / 永久移除
版本语义:pluginId 稳定;packageId 不可变(改代码 = 追加新 Package,不覆盖旧版); currentPackageId = 最近成功;nextPackageId = 待批准/待激活/最近失败。授权:单 ✓ 只放行当前包,双 ✓ 放行该插件未来版本。
2. 操作要点
平台选择
- Host:文件/网络/进程/事件/工具 →
ctx.fs、ctx.on('fs/observed'|'tools/result'|...)、harness。 - Client:主题/布局/页面状态/UI → Slots、
useWorkspaces/useSession等标准 props。 - 数据在 Host 处理、展示在 Client → 双半:Host 暴露 RPC/HTTP,Client 拉取渲染。
包私有 RPC(仅动态插件)
- Host:
harness.handle('method', async (args) => json)。 - Client:
host.call('method', args)—— 请求/响应式,无 Host→Client 推送,实时性靠客户端轮询。 - 只传自有 lossless JSON;禁止传活对象(Service/Event/Session/React 元素)。
发布插件:Client→Host 走 HTTP(webServer)
harness/host是动态沙箱注入,发布插件没有。- Host:
ctx.webServer.register({ kind:'exact', path:'/my-prefix/api', handler(req,res) }),handler 闭包可ctx.fs。 - Client:
fetch('/my-prefix/api?…')同源调用。 - 发布包结构见 AGENTS.md「两种交付形态」。
客户端硬约束(动态)
- 可用全局:
ctx、React(仅 createElement,无 JSX)、host、styles、console。 - 不能
import/require;样式用styles.insert(css);定时器ctx.interval(需inject:['timer'])。
槽位注册
const slots = ctx.get('slots')
slots.inject('conversation.view', () => slots.register(
{ name: 'conversation.view', id: 'my-tab', order: 20, label: () => 'MyTab' },
MyComponent,
))
- 先
Slots.listSubTree(无 root 看拓扑 → 精确 root 看协议/props/占用)。 - 纯新增(replaceRisk:none)优先;不要替换 root/sidebar/conversation 整块。
事件 / 依赖 / 副作用
ctx.on('event', handler)监听;Waterfall 必须return next()。inject:['svc']声明硬依赖(未就绪则等待);可选服务ctx.get('x')判空。- 一切副作用挂 fiber:
ctx.on/ctx.effect/ctx.provide/disposer,停用自动回收。
3. 调试方法
常见失败速查
| 失败 | 检查 |
|---|---|
service "x" is not declared | ctx.x 未 inject → 改 ctx.get('x') + 判空,或声明硬依赖 |
cannot get property "timer" without inject | 用 ctx.interval/timeout 需 inject:['timer'] |
| 客户端解析失败 | 代码里出现 JSX / TS / import / 不可用全局 |
| 槽位注册失败 | 未查活槽子树 / id/key/options 不符协议 |
| UI 报错 | cordis_inspect_self(pluginId, packageId) 读 client-render 诊断 + 栈 |
host.call 失败 | handler 名、当前 pluginRunId、JSON 参数、handler 内真实服务 |
| 激活失败 | 诊断 → 同插件追加修正 Package → update(失败不自动回滚,需显式 run 旧包) |
本项目踩过的坑
- 绝对路径被剥:路径 join 若对每个段
replace(/^\/+/,''),/mnt/...会变相对 → 读文件全部失败。修复:绝对段保留前导/并重置 join。 task.json.id≠ 目录名:trellis 日期前缀任务目录是08-16-<slug>,但 jsonid是<slug>;会话指针引用的是目录名。取规范 id 用目录名,别用 jsonid。- markdown 里 HTML 注释:
<!-- @@@auto:... -->标记默认会当段落渲染 → 渲染器跳过注释块 + 剥行内注释(代码围栏内保留)。 - 勾选框换行:
flex-wrap:wrap把 checkbox 挤到独立行 → checkboxflex-shrink:0+ 文字flex:1; min-width:0。 - 无 Host→Client 推送:动态 RPC 只有请求/响应 → 客户端 5s 轮询(槽位只在激活视图挂载,切走即停)。
验证手法(无需浏览器)
- 宿主解析逻辑可用 Node 脚本对真实数据模拟(复制函数 +
fs.readFileSync)。 - 槽位是否注册:
Slots.listSubTree精确 root,occupants 里应出现dyn/<pluginId>/ id。 - 发布文件:
node --check+import()验证导出{name,inject,apply}。
4. 热加载方法
| 变更对象 | 生效方式 | 是否重建 |
|---|---|---|
| 动态插件代码(cordis_define) | 追加新 Package → cordis_run update(需批准) | 否,运行时加载 |
| 发布包 host(lib/index.js) | 重装 / 组合重启加载 | 否(随组合加载) |
| 发布包 client(lib/client.js) | clientModules 按文件 hash 提供;改后需重建 web 产物 | 是 |
| checkout 内客户端源码(web 壳/普通包) | pnpm run dev:web watcher 重建 bundle,浏览器 HMR | 是(dev 模式) |
| 页面行为 | 动态插件改动后浏览器侧会重载 client 半;保险起见可刷新页面 | — |
关键区分:动态插件由运行时 runner 直接求值(改
cordis_define即可),无需 Vite/dev:web; 只有修改 checkout 内源文件(已发布客户端模块 / web 壳)才需要 dev:web 重建 bundle。dev:webwatcher 必须从同一 checkout 运行才有效。
5. 本项目案例命令序列
# 动态原型(本会话内,Trellis 标签)
cordis_define idPrefix=tred → tred-1/pkg-1 # 首版
cordis_run tred-1 pkg-1 run # 批准
# 修 joinPath 绝对路径 → pkg-2 update;markdown → pkg-3;注释隐藏 → pkg-4;勾选框布局 → pkg-5
cordis_run tred-1 pkg-5 update # 每版都需批准
# 发布包(仓库根目录即 bundle)
# package.json: dsh.bundle.patch + dsh.client + exports["."]/["./client"]
# lib/index.js (host: export {name,inject,apply}, webServer 路由)
# lib/client.js (client: __ModuleLoader__ bundle, fetch)
dsh plugin --profile web add /path/to/dsh-trellis-dashboard # 部署
# 发布到 npm(README 中英双语;产物只含 lib/、cordis.patch.yml、README*、LICENSE,
# .trellis/.claude/.codex/.agents/plans/docs/test 等私人/开发文件由 files 白名单排除)
npm run check && npm test # prepublishOnly 会自动做,手动跑一次亦可
npm publish # 需先 npm login;发布后用户:
# dsh plugin --profile web add dsh-trellis-dashboard
# 本包无构建步骤(lib/ 已提交 git)→ GitHub 安装无需 prepare/allowBuilds