Replies: 3 comments
|
补一组量化结果,把上面第 4 节的三个请求从"提议"变成有具体收益的提案。 我写了一层纯裁决函数(零 harness 依赖,可离线回放),对全部扫描语料重放了一遍。代码和数据都在 https://github.com/anweat/dsh-ecosystem-conflicts/tree/master/arbitration 语料9,617 条记录 / 8,540 个去重包名 / 52,301 个贡献 / 1,201 个争用格。 现状(全部同装)581 个格会让注册表抛错,牵涉 896 个包(10.5%)。 任一个就足以让整个组合启动失败。 裁决后
裁决动作: 成对共存更贴近实际——没人会装 9,617 个插件,但装两个撞车的插件天天发生。取样 4,000 对今天会互相炸的组合,3,941 对(98.5%)裁决后可共存,59 对(1.5%)仍有前端功能损失。 两个我觉得值得单独指出的结论1. 581 起工具名冲突全部解为 2. 剩余的功能损失 100% 集中在前端。 也就是说上面第 4 节第 3 项( EnglishFollow-up with numbers. I wrote a pure arbitration function (no harness dependency, replayable offline) and ran it over the whole corpus — code and data at https://github.com/anweat/dsh-ecosystem-conflicts/tree/master/arbitration Corpus: 9,617 records / 8,540 distinct package names / 52,301 contributions / 1,201 contended cells. Today, everything installed: 581 cells would make a registry throw, involving 896 packages (10.5%). One is enough to fail the boot. After arbitration: 77.4% Pairwise: of 4,000 sampled pairs that are mutually fatal today, 98.5% coexist after arbitration. Two conclusions worth separating out:
|
|
再补一次结果:把裁决真正跑起来了,而且规模测试发现了一个我读源码时没看出来的冲突类别。 代码与数据仍在 https://github.com/anweat/dsh-ecosystem-conflicts 全语料对真注册表的实测不是离线重放,是把整个语料灌进真的 553 个争用工具全部解析到裁决赢家,全部保持原名,全局层保持为空。 这些组合今天任意两个装在一起就会让启动失败。 规模测试发现的:保留名是一个独立的冲突类别有一个包注册了名为 scope 分层对它无效。 它不是"这一层被占了",而是任何层都拒绝。这和其余 553 起工具名冲突是不同性质的东西,而我此前把它们归成了一类。裁决现在单独建模为 这个类别只有把全语料灌进真注册表才会暴露;读源码时我见过那段代码,但没意识到它是独立类别,合成的两插件用例也永远碰不到。 两个纠正了我们自己假设的发现这两条会约束这个方向上的任何设计,所以单独列出: 1. 挂 整个组合根本没启动到有 agent 的阶段。守门员必须读 entry list——它在那些 fiber 应用之前就已完整,而且冲突从中即可预测。 2. 一个预设就是一个 scope。 与三个改动请求的关系上面第 4 节的三项,现在都有了对应的实测数字:
EnglishFollow-up with the arbitration actually running, and one conflict class that only a full-scale run revealed. Full corpus against the real registry — not an offline replay: 896 real scopes on one chain via real All 553 contended tools resolve to the arbitrated winner, under their original names, with the global layer left empty. Scale found a class reading the source did not. One package registers Two findings that corrected our own assumptions, since they constrain any design here:
On the three requests: I should correct myself on ordering — I had said event order was largely untreatable, and that was wrong. |
|
第 3 项( 原型:两个文件,37 行第二处接在 const seeded = registrant === undefined ? undefined : this._defaultPriority.get(registrant)
const erased = {
...options,
...(registrant !== undefined ? { registrant } : {}),
...(options.priority === undefined && seeded !== undefined ? { priority: seeded } : {}),
}纯增量:插件自己声明的 它买到了什么同一份语料、同一套裁决,只把"客户端争用座位"的补救从"撤下整个插件的前端半"换成"普通遮蔽": 剩下的那 1 例是保留名( 行为验证原型在真
第三条是这个改动的实质:今天的取舍粒度是"整个插件",有 rank 之后是"那一个座位"。 EnglishI built a working prototype of request 3 and measured what it buys. Patch: https://github.com/anweat/dsh-ecosystem-conflicts/blob/master/experiments/bootpluginrow-priority.patch Two files, 37 lines. Same corpus, same arbitration, only the client-seat remedy changed from withholding a plugin to an ordinary shadow: The one remaining degraded package is the reserved-name case, which no ranking fixes. Everything else the client plane costs today is this single missing field. Behaviour verified against the real That third point is the substance of the change: today the unit of resolution is a whole plugin, and with a rank it is one seat. |
Uh oh!
There was an error while loading. Please reload this page.
背景
我扫描了 GitHub 上 12,630 个 dsh 相关仓库,其中 9,873 个是真实插件(带
package.json#dsh声明、cordis.patch.yml,或含实际注册调用)。分析是静态的:用与 dsh 相同的applyEntryPatches算法重放各插件的补丁,再从源码/构建产物提取注册调用点。完整数据、方法与可复现脚本:https://github.com/anweat/dsh-ecosystem-conflicts
结果里有一个结构性问题,我想先确认理解是否正确,再提三个具体的小改动。
一、生态整体站在了 host 平面
没有任何一个第三方插件挂在分组下。
而出厂架构把模型可见的行放在 agent 平面。定量佐证:web-app 在根上禁用的 24 行,22 行在
standard预设里重新挂上(例外只有hmr和tool-str-replace-editor)。dsh-agent-presets也会对未加入预设的 agent 告警,说它"解析自空的全局层"。也就是说全局层设计上应该是空的,而整个生态都挤在里面。我理解这不是生态不守规矩,而是两个平面的存在没有被插件作者感知到。
二、由此产生的冲突
tools.register抛错 → 启动失败最挤的工具名:
country_info84 个包、element_info55、bash44、memory_search37。最挤的 entry id 全是基础设施:
storage29、storage-json29、storage-domain29、agent-presets26、code-runtime25 —— 插件在各自重新插入宿主本该提供的东西。另外:2,622 个包同时注册一个 UI 槽位和一个 HTTP 路由,即所有人都在手搓"一个面板 + 喂它数据的端点"。这是最高频的组合,断层第一。
三、大部分可以在插件侧解决
我在一个独立克隆里做了机制验证(33 项断言全部通过,脚本在上面仓库的
experiments/):isolate是 loader 的一等 entry 选项,cordis:group+isolate能让 shim 拦在消费者和真服务之间,纯配置声明,零上游改动view()已有"近的遮蔽远的"语义,不需要改名,链序即优先级tools.guard()免疫 waterfall 短路,所以自定策略是顺序无关的所以我不打算请求大的架构改动。但有三处配置层确实够不到,想提出来讨论。
四、三个具体的小改动
1. 事件监听的数值优先级
EventOptions目前只有prepend?: boolean(二元),而 1,951 个插件在干预回合或工具管线(agent/pre-step513、tools/pre-execute349、llm/stream256)。顺序 = 注册顺序 = 激活顺序,配置层不可声明。更麻烦的是 waterfall 的短路语义:任何监听器不调
next()就静默截断整条链,349 个包共用tools/pre-execute,其中任一个早注册并短路,下游全废。建议:
EventOptions增加priority?: number,派发前稳定排序。register()现在已经在unshift/push二选一,改成有序插入即可。2. 短路可观测(改动最小)
不改变任何语义,只在监听器短路时发一个诊断信号(或 dev 模式记录)。现在这类失效没有任何痕迹,插件作者无从排查。
3.
BootPluginRow携带 priority客户端槽位本身有完整的 priority 遮蔽语义,但
BootPluginRow只有{ id, inject, immediately },客户端loader.create({ name })没有配置缝。结果是前端冲突只能"二选一禁掉一个",而不是分层让位。建议:
BootPluginRow扩展priority?: number,主机侧清单生成时写入,客户端透传。改动集中在packages/client/modules。五、说明与征求意见
数据的局限我在仓库 README 里列明了:路由部分是 503 个仓库的抽样且 37% 静态不可判定;路由争用以分叉为主而非独立撞车。被列入
data/conflicts.json不代表对该插件的评判——主因是结构性的,大多数插件单独使用都正常。主要想先确认:上面对两个平面的理解是否正确?如果我理解错了,后面的结论都要重来。
第 3 项如果有兴趣,我可以先做一个本地原型验证可行性。
English summary
I analyzed 12,630 dsh-related repositories, of which 9,873 are real plugins. Method, data, and reproducible scripts: https://github.com/anweat/dsh-ecosystem-conflicts
Structural finding: 9,216 entry rows insert at the root, zero insert under a group. The shipped design puts model-facing rows on the agent plane — 22 of the 24 rows web-app disables at the root are re-mounted in the
standardpreset — so the global layer is meant to be empty, yet the whole ecosystem registers there.Consequences: 553 tool-name collisions + 28 against shipped tools (both throw at registration → boot failure), 452 entry-id collisions, 89 orphan patches that fail silently, 77 contended config rows. The most contended entry ids are all infrastructure (
storage,agent-presets,code-runtime) — plugins each re-inserting what the host is supposed to provide.Most of this is fixable plugin-side:
isolateas a loader entry option lets a shim sit between consumer and service with no upstream change, and scope layering resolves tool names without renaming anything the model sees. 33 assertions verifying this are inexperiments/.Three things the config layer cannot reach:
EventOptionsnumericpriority(currently binaryprepend; 1,951 plugins intervene in the turn/tool pipeline, and any waterfall listener can silently cut the chain)BootPluginRowcarryingpriority, so client-side slot conflicts can shadow instead of forcing an all-or-nothing disableMainly want to confirm the two-plane reading is correct before building on it.
All reactions