架构说明 (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.emit、ctx.plugin、ctx.registry—— 因此工作室"凭空产生事件"通过 自有 bus(api.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 clone到installed/<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(体积/产物约束)。