[RFC] dsh 社区插件互操作标准 v0.15 —— Manifest、Capability 协商与事件契约(社区讨论稿,征求意见) #2714
hikariming
started this conversation in
Ideas
Replies: 6 comments
|
📎 相关链接汇总(讨论原文 / 参考实现 / 外部规范) 讨论原文(本 RFC 的形成过程)
标准文档与调研(dsh-community-fabric,MIT)
参考实现 / PoC
涉及的外部规范
对 RFC 正文有意见请直接在本 discussion 回复;想参与具体某份子 RFC(0002/0003/0004)的评审,可到 omdsh-dev/community 开 issue 认领。 |
0 replies
|
蓝鲸插件全体成员期待尽快形成标准。 符合标准的插件 ,蓝鲸索引库,第一时间所引并收录(可能需要你提个PR) |
0 replies
|
确实,在开发插件的时候正文提到的问题大部分都遇到过,deepseek 还建议给上游提 issue~ 没想到这么快就有大佬整理总结出来了 👍 |
0 replies
|
支持这个标准方向。我们手上的四个插件(dsh-whale-musume / dsh-statusbar / dsh-windows-notify / dsh-sandbox-tester)愿意作为早期实现方:按 Manifest / Capability / 事件契约适配,并把落地中遇到的坑反馈回 RFC。标准越早有几个真实实现打样,迭代越快。 |
0 replies
|
有关于 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
RFC: dsh 社区插件互操作标准 v0.15 —— Manifest、Capability 协商与事件契约
本文由 dsh 社区生态开发者共同讨论形成。感谢 btspoony、morlay、mattheliu、shine-233、r05En1cU、Lipraty、qing3a、T-Auto、Yan-Zero、Qiuner、t4wefan 等所有参与讨论的开发者。对正文有意见请直接回复;想认领某份子 RFC(0002 / 0003 / 0004)的评审,到 omdsh-dev/community 开 issue 即可。
# RFC: dsh 社区插件互操作标准 v0.15 —— Manifest、Capability 协商与事件契约0. 一句话摘要
给 dsh 生态定一套与 dsh 上游版本解耦的插件标准:插件用一份静态 manifest 声明"我是谁、我需要什么能力";宿主(GUI / Web UI / TUI / 启动器)先协商、再授权、再按统一的生命周期激活插件。做一件事只有一种明确的方法。
类比一句话就能懂:我们要做的是 Chrome 扩展那套(manifest + 权限声明 + 统一 API),而不是每家浏览器自己发明一遍插件机制。 Minecraft 社区用 Forge/Fabric 证明过:即使官方不参与,社区标准也能成为事实标准。
v0.15 相比 v0.1 主要变了三件事(§5 有完整清单):
ContentBlock:行业已经收敛到这个结构了,我们不自造格式。范围仍然刻意很小:没有沙箱承诺、没有可修改拦截、没有插件间 service、没有跨端 UI——这些全部显式延期为独立 RFC(§6)。
1. 我们要解决什么问题
dsh 火了之后,社区自发长出了三种主要终端(GUI、Web UI、TUI)、若干启动器和分发渠道,awesome 目录快照已经收录了 3,809 个插件仓库。生态很繁荣,但底下有三条裂缝,而且都在变宽。
第一条:插件靠 patch 活着。 我们对 12 个代表性开源插件做了源码调研([完整报告](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md)),多数插件靠源码 patch、monkey patch、内部事件名或
ctx.get()反射探测来实现功能。这不是插件作者的错——官方接缝缺失时他们别无选择——但结果是 dsh 每更新一次,生态就批量炸一轮。分发渠道被迫用"插件 A@0.1 + B@0.3 + dsh@0.5.0"式的整包锁版本对抗接口不稳定,治标不治本。历史还演过一次更惨的:早期大家各写各的 loader,官方引入统一注册方式后,所有第三方 loader 一夜全废。教训很清楚:依赖实现的生态会死,依赖标准的生态才能穿越上游的更新周期。第二条:装上才知道炸。 现有 manifest 只有包名和 patch 文件列表。宿主、市场、启动器都无法在执行代码前回答最基本的问题:这个插件需要图形界面吗?要读会话吗?要联网吗?能在 TUI 上跑吗?用户在 TUI 里装一个需要 GUI 的插件,唯一的"报错方式"是崩溃。
第三条:谁后加载谁赢。 多个插件改同一个行为时,没有声明、没有顺序、没有冲突规则,实际仲裁者是加载顺序。插件互相打架时,用户和排障者都回答不了"这个东西到底是谁改的"。
上游改内核的意愿和节奏我们控制不了,社区也明确不接受激进的内核改动。所以本标准的第一原则是:标准的存续不依赖 dsh 上游的任何决定。
2. 三轮讨论收敛出的八条原则
每条背后都有具体反例撑着,出处见附录 A。
① 静态可分析。 manifest 是包根目录里的一个静态 JSON 文件,禁止运行代码生成,宿主加载时也不从网络取 schema。这样市场、启动器、校验工具不用执行插件代码就能完成发现、校验和协商——"装上才知道炸"从机制上消失。
② 五类声明不混淆。 "我依赖什么"(requires)、"我申请什么授权"(permissions)、"我能给别人提供什么"(provides)、"我给产品贡献什么静态元数据"(contributes)、"我想订阅什么事件"(subscriptions)是五种不同的语义,不能压进一个泛化的 capabilities 容器里。v0.15 的 schema 只接受已有具体契约的子集,并且直接拒绝
provides和requires.services——插件间服务的组合规则还没定(归 RFC 0003),定好之前不开口子。③ 协商 + 诚实降级。 required 能力缺失 → 装/激活之前就明确拒载,并用人话解释原因("该插件需要图形界面能力,当前终端不支持");optional 能力缺失 → 走插件声明过的降级路径。市场对每个插件只展示五种状态,且不许互相升级:声明兼容 / 等待授权 / 已实测 / 不兼容 / 未知。"声明兼容"永远不等于"已实测",更不等于"安全"。
④ capability 不是沙箱,这话必须说在明处。 v0.15 是 trusted-in-process 档位:插件和宿主跑在同一个 Node.js 进程里,受信任的代码在技术上完全可以绕过
ctx直接调系统接口。capability 声明服务于兼容判断、用户授权和事后审计,不构成安全边界——宿主必须显著公示这一点,不许把"声明过了"包装成"被拦住了"。真正的隔离执行(进程/realm 隔离、受控 IPC)是独立的后续 RFC。⑤ 上游变化收敛到 Adapter,扛不住就明说。 插件只依赖稳定契约;版本化的 DSH Adapter 是整个体系里唯一允许 import 上游 runtime 的层,也是唯一吸收上游变化的地方。上游哪天不再暴露某项能力需要的观察点,Adapter 必须下线对应 capability 并报告原因——不能用私有 patch 猜语义,返回一个"看起来成功"的近似结果。运行时方法替换这类技术(dsh-neoforge 的 mixin PoC)只能作为锁定版本的 Adapter 实验存在,它的私有 target 永远不进插件可见的 API。
⑥ 确定性与可归属。 加载顺序永远不做冲突仲裁。所有标准注册都经过 Broker,归属到"哪个插件的哪一次激活",并记进一份最小 effect ledger——排障时能直接回答"这个命令/面板/残留资源是谁创建的,停用之后清理干净了没有"。
⑦ 元协议内核,领域契约各自升级。(v0.15 新增)协商内核是领域无关的:它只认
apiVersion + kind形式的契约引用和 requires/supports 声明,本身不含任何业务字段。commands、storage、messages——以及未来的 model provider、tool、settings——每一项都是独立版本化的契约,自己演进。为什么要这样:Model Provider、工具渐进式披露、Response/Reasoning 字段这些东西是上游模型生态推着跑的,演进极快,中心化的固定 SDK 扛不住这个节奏。某个领域升级时,只有那份契约和用它的插件需要动,内核、无关插件和宿主都不用重新发版。⑧ 参考实现不是标准。(v0.15 从验收标准提升为原则)Fabric、Desktop、TUI 都只是参考实现和一致性证据的提供方。一个行为只有写进规范文本 + registry + fixtures + 一致性测试才算契约;任何单一实现的行为——包括参考实现——都不自动成为标准。
3. 架构与对象模型
3.1 四层架构
官方现有的 package manifest、Cordis service、slot、profile 组合机制原样继续工作,一行都不用改。
3.2 元协议内核:内核只认"面单",不管"包裹"(v0.15 新增)
v0.1 把三项 capability 的定义和协商器写在一起。第三轮讨论指出这是个中心化发版瓶颈:任何一个领域标准微调,主 SDK 和 Broker 就得发新版,然后全生态跟进——这恰恰是我们批评过的"整包锁版本"的翻版。
v0.15 把它拆开。可以把内核想成快递系统:它只认面单格式,不管包裹里装的是什么。
apiVersion + kind契约引用、做 requires/supports 匹配,最后输出一份机器可读的协商报告。宿主、市场、启动器、CI 消费的是同一份报告格式。commands.dsh/v1alpha1Commandcommandsstorage.dsh/v1alpha1LocalStoragestorage.localmessages.dsh/v1alpha1MessageObservermessages.observev1alpha1老老实实标明这是实验期(可能 breaking),不伪装成稳定1.x。私有扩展继续用组织命名空间(x-org.example.*),Registry 里为官方保留了命名空间——未来官方能力可以直接以一等身份入驻。这次重构不改变 v0.15 的能力范围(还是那三项),只改变它们的标识和版本化方式。已有一个独立探索实现验证了这条路:[Yan-Zero/dsh-std](https://github.com/Yan-Zero/dsh-std)。
3.3 Facet 对象模型:插件的"分身"(v0.15 新增方向)
调研里最扎眼的数字:12 个样本插件里 9 个同时需要宿主侧逻辑和客户端呈现。跨面(face)不是少数特例,是有一定复杂度的 dsh 插件的常态。v0.1 只有一个
entrypoints.host,到了 TUI / Web / Remote SSH 场景必然捉襟见肘。v0.15 引入四级对象模型作为规范术语:
打个比方:Component 是你下载的那个安装包;Facet 是插件派驻到不同位置的分身——host 分身跑在宿主侧管逻辑,client 分身贴着界面管呈现,worker 分身在后台干重活;Activation 是某个分身的一次"上岗",上岗期间创建的所有资源都记在这次上岗名下,下岗时统一回收;Participant 是上岗时去和 Broker 谈判的代表——"我需要这些能力,我能提供这些东西"。
v0.15 只规范
hostfacet 的契约;client和worker作为保留名,它们的契约要和 Runtime / Presentation 分层一起在 RFC 0002 里定——Remote SSH 反例已经证明"代码在哪执行、界面长什么样、谁有权批准"是三个独立维度,靠isRemote/hostType这种字段修补只会埋新雷。插件代码长这样(已在 dsh-codex 的便携化重构里跑通,见[分支](https://github.com/Yan-Zero/dsh-codex/tree/agent/std-facet-runtime)):
插件只依赖标准 Facet 上下文,通过契约扩展点发布能力,生命周期由作用域自动回收——不碰宿主私有 API,不被任何特定运行时绑死。
3.4 一个最小 manifest 长什么样
{ "$schema": "https://dsh-std.example/schemas/dsh-plugin/v0.15.json", "id": "com.example.better-sidebar", "name": "Better Sidebar", "version": "1.2.0", "manifestVersion": "0.15", "facets": { "host": { "entry": "dist/host.js", "apiVersion": "v1alpha1" } }, "requires": { "contracts": [ { "apiVersion": "storage.dsh/v1alpha1", "kind": "LocalStorage" }, { "apiVersion": "messages.dsh/v1alpha1", "kind": "MessageObserver", "optional": true } ] }, "permissions": [], "contributes": { "commands": [{ "id": "com.example.better-sidebar.toggle", "title": "Toggle Sidebar" }] }, "subscriptions": ["messages.observe"] }两个容易被问到的细节:
plugin.json——那个名字已经被 [Agent Plugins Specification](https://agent-plugins.org/) 占了。一个包可以同时携带两份文件、支持两套生态,互不干扰。contributes里的 id 用反向域名风格并要求全局唯一,所以校验器能做跨插件的静态冲突检测:两个插件贡献了同一个 id,装之前就报"冲突,不能共存",而不是等加载时互相覆盖。4. v0.15 的精确范围
原则不变:每一项进入范围的能力,都必须同时有 schema、fixture 和能在 headless 环境跑的一致性测试。给不出测试的能力,一律延期。
4.1 交付物清单
dsh-plugin.jsonManifest Schema$schema必填;含facets结构(v0.15 仅规范host)discover → validate → negotiate → authorize → activating → active → deactivating → disposed;以 runtime generation 为作用域的 eager 激活(无按需激活);正常关闭 best-effort 停用,插件清理必须设计成可重复执行4.2 三项领域契约细则
storage.local—— 插件私有持久化,按 Component 隔离。不提供跨插件共享:共享存储本质是插件间组合问题,归 RFC 0003。commands—— 只支持 flat action leaf:一个全局唯一 ID 对应一个 handler,完事。没有 command tree、没有交互式 prompt、没有流式输出——这三样全都依赖 Runtime / Presentation 分层(Remote SSH 场景下子命令树丢在半路的具体反例见 #23 讨论),归 RFC 0002。messages.observe—— 不可修改的消息观察事件,带版本化信封:eventId、scope 内单调序号、privacyClass、裁剪摘要、不可变 payload。(v0.15 新增)payload 里的消息内容采用与 MCPContentBlock对齐的结构——理由很简单:ACP / MCP / ToolCall 返回已经在收敛到这个结构了,我们自造一套格式,收益是零,代价是传递信息丢失外加一层多余的序列化。对齐的精确字段边界是本轮征求意见的重点(§9 第 3 问)。4.3 版本模型
六个版本维度,不许混成一个字段:插件自身
version|manifestVersion(manifest 结构)|facetapiVersion(要求的 Host API 范围)|**各领域契约版本(v0.15 起随契约坐标独立演进)**|宿主产品版本|SDK 发布版本。v0 阶段按"minor 可能 breaking"的实验规则明确标注。4.4 验收标准与表述边界
v0.15 从 Draft 晋级,需要至少两个独立宿主产品/集成(可以共享同一个版本化 DSH Adapter,但集成与 descriptor 证据必须独立)与三个示例插件跑通同一组 headless 场景。dsh-TUI 已认领第一个标准兼容宿主的落地(Manifest 严格校验、协商拒载提示、生命周期顺序、机器可读能力清单,与溯源记录联动)。
表述边界划死:宿主只能说"通过 v0.15 Host conformance",插件只能说"通过 v0.15 plugin validation"——谁都不能说"安全插件"或"官方认证"。
5. v0.1 → v0.15 变更记录
第三轮讨论(#24 下 5 条评论)的全部意见与处置:
host,client/worker保留名归 RFC 0002ContentBlock对齐,避免信息丢失与序列化开销(morlay)x-web.panel.urlState),带字段白名单、大小上限、按插件隔离、禁存 secret;不进核心,不强加给 TUI / headless 宿主(采 Qiuner 建议)首轮 13 条评论 → v0.1 的处置见附录 A。
6. 明确不在 v0.15 的内容
下面每一项都是"方向有价值,但硬塞进当前版本会埋雷",已拆成独立 Draft RFC 分别审查,不会暗中扩大 v0.15:
before-*事件before名字不解决任何问题provides/requires.services)与确定性组合presentation.urlState)net.*/fs.*/ 会话写入7. 与 dsh 官方的关系
7.1 我们不请求什么
7.2 我们请求什么
dsh-plugin.json文件名、*.dsh/*契约坐标、dsh-*前缀,是否与官方现有或近期规划冲突。Registry 已为官方保留命名空间,未来官方能力可直接以一等身份入驻。7.3 对官方的价值
3,809 个插件仓库背后的 patch 与内部接口依赖,目前每一次都由官方更新"背锅"。互操作层落地后,兼容压力从"官方 vs 所有插件"收敛为"Adapter 一个点",上游迭代的生态阻力显著下降;兼容信息静态化后,"装上就炸"类负面体验不再归因于 dsh 本体。标准由社区治理并承担维护成本;官方任意时点采纳的成本都很低(映射一层 Adapter),不采纳也不受损。
8. 落地计划
messages.observe(ContentBlock 信封)+storage.local+ flatcommands;故障 / 重复 ID / 取消 / 关闭 fixtures;两宿主 × 三插件互操作证据(dsh-TUI 已认领首个 Host conformance)client/workerfacet 契约定案9. 本轮最希望得到反馈的五个问题
dsh-plugin.json、*.dsh/*坐标、dsh-*前缀,与官方现有或规划有冲突吗?messages.observepayload 边界:对齐 MCPContentBlock时,哪些字段进 v0.15 信封、哪些裁剪?privacyClass分级怎么定最不容易被误用?apiVersion + kind(§3.2)vs 更简单的平面能力名——前者换取独立演进与跨版本适配,代价是初期理解成本。倾向、反对或替代方案都欢迎。host/client/worker三个保留名够用吗?Remote SSH 与 headless 场景下有没有第四种运行面?附录 A:首轮讨论(#23 评论)→ v0.1 处置摘要
plugin.json与 Agent Plugins 规范冲突(btspoony)dsh-plugin.jsonapiVersion + kindbefore-*仍不进核心注:"已采纳"表示被当前 Draft 文档采纳,不表示所有参与者已形成正式共识——正式共识由治理流程(RFC 0000)产生。
附录 B:原文与参考实现索引
讨论原文:首轮 RFC 与 13 条评论 [community#23](https://github.com/omdsh-dev/community/issues/23);v0.1 定稿与第三轮 5 条评论 [community#24](https://github.com/omdsh-dev/community/issues/24);首轮逐条处置记录 [community-issue-23-review](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/research/community-issue-23-review.zh.md)
标准文档与调研(dsh-community-fabric,MIT):[总入口](https://github.com/anywhere-labs/deepseek-harness-desktop/tree/master/dsh-community-fabric);[[4 份 Draft RFC](https://github.com/anywhere-labs/deepseek-harness-desktop/tree/master/dsh-community-fabric/docs/rfcs)](https://github.com/anywhere-labs/deepseek-harness-desktop/tree/master/dsh-community-fabric/docs/rfcs);[[兼容层架构](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/architecture/compatibility-layer.zh.md)](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/architecture/compatibility-layer.zh.md);[[插件需求调研](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md)](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md);[[成熟框架调研](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/research/mature-plugin-frameworks.zh.md)](https://github.com/anywhere-labs/deepseek-harness-desktop/blob/master/dsh-community-fabric/docs/research/mature-plugin-frameworks.zh.md)
参考实现 / PoC:元协议内核探索 [Yan-Zero/dsh-std](https://github.com/Yan-Zero/dsh-std);Facet 模式验证 [Yan-Zero/dsh-codex@agent/std-facet-runtime](https://github.com/Yan-Zero/dsh-codex/tree/agent/std-facet-runtime);运行时 mixin PoC [r05En1cU/dsh-neoforge](https://github.com/r05En1cU/dsh-neoforge);静态校验工具 dsh-plugin-verify(qing3a);TUI 参考宿主 [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI)
外部规范:MCP
ContentBlock(§4.2 对齐目标);[Agent Plugins Specification](https://agent-plugins.org/)(`plugin.json` 避让原因)本文由 dsh 社区生态开发者共同讨论形成。感谢 btspoony、morlay、mattheliu、shine-233、r05En1cU、Lipraty、qing3a、T-Auto、Yan-Zero、Qiuner、t4wefan 等所有参与讨论的开发者。对正文有意见请直接回复;想认领某份子 RFC(0002 / 0003 / 0004)的评审,到 [omdsh-dev/community](https://github.com/omdsh-dev/community) 开 issue 即可。
All reactions