架构说明 (architecture.md)

August 27, 2026 · View on GitHub

1. 为什么需要这个插件

DSH 的插件系统是 Cordis 插件 + Agent preset/session 组合。对新手来说,插件"看不到、摸不着": 只能靠反复对话迭代。工作室把运行时反成可见状态(事件、插件、装载表、签名)并补上 低代码开发持久化管理

2. 依赖的 Cordis / DSH 机制(全部来自运行时真实契约)

机制用途位置
internal/dispatch (mode, name, args, thisArg)捕获所有非 internal 事件分发(emit/waterfall/serial/parallel/bail)@deepseek-ai/cordis EventsService.dispatch
internal/listener统计每个事件的监听器注册数EventsService.on
internal/plugin / internal/status插件生命周期监控(pending/loading/active/failed/unloading/disposed)Fiber
typert.listPackages()反射所有包的服务/事件模型(签名/JSDoc)——一览表与下拉的反射源dsh-typert-registry
loader.entries()/update()/create()/remove()系统分区装载表 + 启停/注册/卸载(持久化到部署配置)cordis-plugin-loader
pluginInventory.list()只读装载表快照dsh-host-plugin-inventory
settings.prepareDocument()定位 DSH home(<home>/settings.yaml 的目录 = DSH home)dsh-settings-file
fs (resolve/readText/writeText)持久化(原子写 + 自动建目录)dsh-fs-local
shell (resolve/run)git clone 安装 GitHub 插件dsh-shell
timer触发器定时器/监控窗口cordis-plugin-timer
客户端 slots (sidebar.footer.action / shell.overlay)拼图图标入口 + 全屏面板dsh-client-ui-*

3. 动态插件沙箱约束(Host)

动态 Host 半部运行在 node:vm 沙箱并拿到受限 ctx 门面

  • 只能 ctx.on / ctx.once / ctx.provide / ctx.effect + 注入服务属性 + ctx.get(name) + ctx.tools.*
  • 没有 ctx.emitctx.pluginctx.registry —— 因此工作室"凭空产生事件"通过 自有 busapi.bus.emit → 走进事件流 ring,标记 mode=bus)实现;对外"触发"则调用 真实服务(web/timer 等)。这是一个有意的工程折衷:宁可 bus 事件可观测,也不绕过沙箱。
  • new Function 在沙箱内可用(vm realm 标准内建)→ 监听器脚本/触发器脚本按此编译执行, 全程 try/catch + 错误记录(rt.errors),waterfall 出错自动回落到 next()(默认行为)。

4. 数据模型(持久化,均为 JSON,原子写)

<storeRoot>/
├── catalog.json      { version, plugins: [ { id, name, kind: listener|trigger|external,
│                       hooks: [{ event, mode, params:[{name,desc}], body, enabled }],
│                       trigger: { everySeconds, body }, autoStart, source, entryId } ] }
├── sets.json         { version, sets: [ { id, name, members: [{kind: plugin|set, id}] } ] }
├── state.json        { version, cap, lastSnapshot: { at, loader:{id:enabled}, catalog:{id:bool} },
│                       restorePrompt, autoSnapshot }
├── annotations.json  [ [event, "a, b, c", "a: 说明; b: 说明"], ... ]   ← 需求中的"表头"格式
├── ctx-templates.json [ { label, code } ]                              ← 需求中的 ctx.get(...) 模板
├── dev-packages.json { version, packages: [...] }                      ← 开发中插件包(权威副本)
└── dev-packages/<id>/manifest.json + listeners/<hookId>.js             ← 与本地文件对应(镜像)

storeRoot 解析顺序:workspace 相对 dsh-plugin-studio-data/(尊重部署 fs 写策略) → DSH home ~/.dsh/dsh-plugin-studio/(由 settings 文档路径推导)→ 兜底。

5. 监听器编译与 waterfall 安全规则

编译: new Function('ctx','emit','console','next',
       'return async function __hook(' + params.join(',') + ') { ' + body + ' }')
包裹(waterfall): 传入 wrappedNext 记录 called;body 未调用 next 且返回 undefined → 自动 next();
                 body 抛错 → 记录 + next()(绝不破坏 DSH 默认行为)
其他模式: try/catch 记录错误,返回值透传

6. 插件一览表的"反射"语义

  • 事件目录:typert 反射(包 → 事件名/mode/签名/说明)∪ 持久化 annotations(覆盖/补充说明), 下拉列表同源。
  • 插件 → 事件边:工作室自己的 catalog 插件是精确已知的;外部插件监听关系无法静态反射 (Cordis internal/listener 回调不携带注册者纤维,hook.ctx 恒为事件总线所有者)。 当前实现为:精确边 = studio 插件;系统插件显示装载状态+接口(package 反射), 精确"谁在监听"留作后续(typert 贡献式声明 API)。
  • 插件生命周期internal/plugin/internal/status → 每个 fiber 的 uid/name/state。

7. loader 与"系统/动态分区"

  • 系统分区 = loader entries:loader.update(id, {disabled}) 切换(写回部署配置,UI 有提示)。
  • 动态分区 = studio catalog:启停=ctx.on 注册/注销;GitHub 安装=shell git cloneinstalled/<slug>,再 loader.create({name: path}) 装载(该步骤执行真实模块导入, UI 明确标注风险)。
  • 组合嵌套:set.members 支持 {kind:'set'},flatten 带环检测;状态 = 全部启动绿 / 全停红 / 部分黄。

8. 状态恢复

recordTool 当前启停state.json.lastSnapshot。工作室每次启动时对比当前 loader+catalog 状态,不一致→面板顶部横幅"是否恢复?"(恢复/忽略),恢复=批量 apply。

9. 限制与后续

  • 客户端生产打包需要 DSH monorepo 的 web 构建管线(dsh.client 扫描/umd 化); 仓库内以"动态插件引导 + dist 单文件"形式交付完整功能,loader 生产入口为 dist/host.mjs
  • Git 安装依赖环境存在 git 与 shell 后端(Windows 下为 bash/pwsh 之一)。
  • 代码编辑器为「带行感的 textarea + tab 插入 + 模板插入」,未内嵌 Monaco(体积/产物约束)。