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.fsctx.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「两种交付形态」。

客户端硬约束(动态)

  • 可用全局:ctxReact仅 createElement,无 JSX)、hoststylesconsole
  • 不能 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 declaredctx.xinject → 改 ctx.get('x') + 判空,或声明硬依赖
cannot get property "timer" without injectctx.interval/timeoutinject:['timer']
客户端解析失败代码里出现 JSX / TS / import / 不可用全局
槽位注册失败未查活槽子树 / id/key/options 不符协议
UI 报错cordis_inspect_self(pluginId, packageId) 读 client-render 诊断 + 栈
host.call 失败handler 名、当前 pluginRunId、JSON 参数、handler 内真实服务
激活失败诊断 → 同插件追加修正 Package → update(失败不自动回滚,需显式 run 旧包)

本项目踩过的坑

  1. 绝对路径被剥:路径 join 若对每个段 replace(/^\/+/,'')/mnt/... 会变相对 → 读文件全部失败。修复:绝对段保留前导 / 并重置 join。
  2. task.json.id ≠ 目录名:trellis 日期前缀任务目录是 08-16-<slug>,但 json id<slug>;会话指针引用的是目录名。取规范 id 用目录名,别用 json id
  3. markdown 里 HTML 注释<!-- @@@auto:... --> 标记默认会当段落渲染 → 渲染器跳过注释块 + 剥行内注释(代码围栏内保留)。
  4. 勾选框换行flex-wrap:wrap 把 checkbox 挤到独立行 → checkbox flex-shrink:0 + 文字 flex:1; min-width:0
  5. 无 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:web watcher 必须从同一 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