zfdx123/dsh-atelier--packages-dsh-hooks-ordering ↗★ 0
@zfdx123/dsh-hooks-ordering
钩子排序:为 Cordis 的 waterfall / serial 钩子提供确定性的 before/after 排序,让互相独立的插件能声明彼此的先后关系(拓扑排序、环检测、可配置控制集与设置页)。 适合需要解决多插件间钩子执行先后顺序冲突的DSH开发者。
インストール
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:zfdx123/dsh-atelier#8a3566cc5762e3e6d26b23c75c6ee3c4a336304f&path:packages/dsh-hooks-orderingドキュメント
README 全文を読む ↗配置
三个字段可以直接在 dsh 的设置面板里编辑,页面名为左侧导航中的钩子排序(命名空间 hooks-ordering),无需改 profile:
| 字段 | 含义 |
|---|---|
hooks | 要控制的 waterfall 钩子,一行一个。[] 完全禁用 waterfall 服务。 |
serialHooks | 要控制的 serial 钩子,一行一个。[] 完全禁用 serial 服务。 |
log | 约束 DAG(JSON)日志文件;留空表示「不记录」。顺序一旦看着不对就能派上用场。 |
第四个组装键 syncReturnHooks 刻意不进设置页:它描述的是宿主的派发方式(哪些钩子的返回值被同步消费),而不是用户的偏好,所以它只属于 profile 那一行。表单里给 hooks 填上一个同步返回的钩子是安全的——它会被控制,而 register() 会在插件试图注册参与者时拒绝,并说明原因。profile 行长这样(本包自带的 cordis.patch.yml 就是这个内容,只有在该包没有作为 bundle 注册时才需要手写):
- insert:
- id: hooks-ordering
# 裸包名——见下面的说明。`/dsh` 也能作为插件入口,
# 但那样 dsh 就找不到浏览器端那一半了。
name: '@zfdx123/dsh-hooks-ordering'
config:
# hooks: ['agent/pre-step', 'tools/post-execute'] # 默认:所有返回契约兼容的 dsh waterfall 钩子
# serialHooks: ['agent/turn-stopping'] # 默认:[agent/turn-stopping]
# syncReturnHooks: ['llm/stream', 'session-telemetry/record', 'compaction/summary-error']
# log: './hooks-ordering-dag.json' # 可选的 DAG 日志
这一行必须写包名,不能写子路径。 dsh 会把某一行的 name 映射回一个包,以便找到该包的浏览器端那一半(dsh.client → 设置页),而它的 locatePkgJson 只接受裸包标识符——exactPackageSpecifier('@scope/name/subpath') 返回 undefined,因为切分后得到三段。写成 @zfdx123/dsh-hooks-ordering/dsh 的那一行能完美加载宿主插件,然后悄悄地永远找不到客户端 bundle:没有设置页,而且任何地方都没有报错。这就是插件表面放在根入口的原因。
出于同一类原因,根入口不带 default 导出。 加载器会先用 exports.default ?? exports 规范化导入的模块,然后才应用它,所以一个并非该插件本身的 default 导出会劫持这一行:本包当时把 default(waterfall 服务)挂成了插件,apply 从未运行,结果是服务活着、却没有控制任何钩子,没有 serial 服务,没有设置命名空间,而且依然悄无声息。HookOrdering 从根入口按名字导出,同时仍然是 /waterfall 的默认导出。
关于接线方式,还有四点值得知道:
- profile 那一行是基础层。 解析顺序是 schema 默认值 →
base(这一行;当这一行为空时是内置钩子集合)→ 用户层。所以表单是在你的组装配置之上编辑,「恢复组合默认」会回到组装配置,而不是回到空表单。 - 编辑在重启后生效(
applies: 'restart'),这是有意为之:改hooks/serialHooks意味着在活动钩子上安装或移除包夹监听器,重启能干净地应用这些变更,而不是在派发链运行中途重新接线。设置log本身很廉价,但命名空间是作为一个整体生效的。 - 这个页面是客户端那一半的贡献。 宿主侧的
settings.register只创建命名空间、它的存储和它的描述符;dsh 从浏览器平面渲染设置,所以是client.js把页面注册进settings.section插槽(order: 27)。移除或加载失败客户端那一半,命名空间依然存在——只是没有表单。 settings是硬依赖(export const inject = ['settings']),这是承重的,而非偶然:cordis 会在一个插件声明的服务存在时立刻激活它,而ctx.get('settings')只是读取服务存储,不会建立那个需求。只做探测而不声明,意味着插件会在第一波加载中、宿主提供settings之前就加载,命名空间于是从未被注册——悄无声息,没有表单也没有报错。声明它同时也让 prepend 的包夹在启动流程中落得更晚,而排序保证正希望它们在那里。注册本身是降级而不是让挂载失败:如果它抛错,插件会告警并回退到组装配置,因为一个可选的表单绝不能阻止 harness 启动。
本包面向 dsh。 钩子名是 dsh 的,settings 是 dsh 的服务,所以两者都不做成可选的;/waterfall 和 /serial 才是不带宿主服务要求的入口。