Skip to content

fix: 原版 DSH 壳上 dsh-ui 围栏静默不渲染(inputTriggers 硬注入门控) - #10

Open
linshule wants to merge 2 commits into
omdsh-dev:mainfrom
linshule:fix/optional-inputtriggers-inject
Open

fix: 原版 DSH 壳上 dsh-ui 围栏静默不渲染(inputTriggers 硬注入门控)#10
linshule wants to merge 2 commits into
omdsh-dev:mainfrom
linshule:fix/optional-inputtriggers-inject

Conversation

@linshule

Copy link
Copy Markdown

问题

原版 DSH(0.1.0-rc.6)shell 上,所有 dsh-ui 围栏渲染为普通代码块,控制台零报错(与 issue #6 的"静默失败"模式一致,但场景不同:不是 DOM 表面发现失败,而是整个 client 从未激活)。

根因

client 入口的硬注入声明:

export const inject = ['slots', 'sessions', 'inputTriggers']

cordis 的 inject硬激活门控:声明的服务必须全部被提供,fiber 才会激活。而原版 DSH shell 没有任何插件提供 inputTriggers 服务(全量扫描 boot graph 确认:仅 vision-toolkit 以 ctx.inject(['inputTriggers'], …) 可选订阅它,且其硬注入列表 ['slots','locale','remote','conversation','sessions'] 全部可用)。

于是 genui 的 fiber 永久停在 waiting、apply() 永不执行、渲染器整体未启动 —— 围栏保持代码块,且无任何日志。

矛盾点:apply() 体内早已inputTriggers 当作可选项处理:

const slash = ctx.get('inputTriggers') as InputTriggerServiceContract | undefined
if (slash !== undefined) { …注册 /panel… } else {
  console.warn('[genui] inputTriggers service unavailable; /panel command disabled')
}

硬注入声明与可选用法自相矛盾 —— 声明写错位置导致可选降级代码永远到不了。

修复

inputTriggers 从硬注入列表移除,保留 ctx.get() 可选查询路径:

export const inject = ['slots', 'sessions']
  • 原版壳:fiber 正常激活,DOM 通道接管渲染,/panel 命令禁用并告警一次(行为与 apply() 内现有降级逻辑一致)
  • inputTriggers 的宿主ctx.get('inputTriggers') 仍能取到服务,/panel 命令照常注册 —— 行为零变化

变更文件

文件 内容
src/client/index.tsx inject 列表 + 原因注释
tests/dom-fence.spec.tsx 注入回归钉同步更新(原断言含 inputTriggers,与硬门控语义冲突)
lib/client.js 重建产物(仅 inject 声明一行差异)
CHANGELOG.md 变更记录

验证

  1. 构建tsc -p tsconfig.json + tsdown 均通过
  2. jsdom 端到端(原版壳模拟:无 inputTriggers 服务、DOM 通道路径):围栏被发现并水合成组件 —— callout、chart(柱状图,含柱高/标签/数值)渲染正确;inputTriggers 缺失告警恰好一次;apply() 无异常
  3. 测试套件(本地 Windows 环境):dom-fence.spec.tsx(27)全绿含更新后的回归钉,plugin-genui.spec.ts(7)全绿;其余失败均为环境限制(install-script 依赖 chmod、skill-md 依赖宿主源码树 yaml 版本),与本次变更无关 —— 完整 370 项在 CI(Linux + DSH 源码 checkout)上运行

复现与对照

  • 复现:任何 rc.6 原版 shell + 本插件 0.8.3,发一条带 dsh-ui 围栏的消息即可
  • 对照:vision-toolkit 对同一服务的正确用法(ctx.inject() 可选订阅 + 硬注入只声明必需服务)

@taekchef taekchef left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@linshule 这个根因抓得很准,修复方向我们完全认可——inputTriggers 不该当硬激活门禁,否则原版壳上整个渲染器都不启动。CI 两个 Node 版本全绿,我们本地也跑了全套(270 passed / 102 skipped)✅

在合之前只提一个请求改动(很小):

/panel 注册请从「apply 时查一次 ctx.get」改成 cordis 可选订阅,避免服务到场顺序竞态。

原因:slots/sessionsinputTriggers 往往是不同 bundle 并发提供,apply() 跑起来的那一刻 ctx.get('inputTriggers') 可能是 undefined——这样带该服务的宿主也会把 /panel 静默禁用。我们用最小 cordis 实验验证过:同批注册时两种顺序下 ctx.get 都可能 miss,而 ctx.inject 可选订阅在任意顺序下都能正确注册。

建议这样写(主体逻辑不用动):

export const inject = ['slots', 'sessions']

export function apply(ctx: Context): () => void {
  // ……既有 slots / toolview / dock 注册不变……
  ctx.inject(['inputTriggers'], (scope) => {
    const slash = scope.get('inputTriggers') as InputTriggerServiceContract | undefined
    if (slash === undefined) return
    scope.effect(() => slash.registerSource(
      createPanelSlashSource((sessionId, instruction) => sendPanelInstruction(ctx, sessionId, instruction)),
    ), 'genui: /panel')
  })
}

ctx.inject 的回调本身就是一个小插件 fiber:服务来了才激活、随 fiber 卸载自动 dispose,不需要再往外层 disposers 里塞。这样「没服务→照常渲染、/panel 不注册」「有服务→/panel 一定能注册」两边都不丢。

另外 main 刚合了 #14(issue #13 修复),麻烦顺手 rebase 一下 main(CHANGELOG / lib/client.js / dom-fence 测试有冲突),并保持 lib/client.js 重建提交。

改完这两点我们立刻合,辛苦啦 🙏

client 入口硬注入声明把 inputTriggers 当成激活前置,但 cordis 的 inject
是硬激活门控:原版 DSH 壳没有任何插件提供该服务 → fiber 永久 waiting、
apply() 永不执行 → 渲染器整体未启动,围栏保持代码块、控制台零报错。

apply() 体内早已用 ctx.get('inputTriggers') 可选降级(缺失仅禁用 /panel
并告警),硬注入声明与可选用法自相矛盾。修复:从硬注入列表移除
inputTriggers,保留可选查询路径;带该服务的宿主行为不变。

- src/client/index.tsx: inject 列表 + 原因注释
- tests/dom-fence.spec.tsx: 注入回归钉同步更新
- lib/client.js: 重建产物
- CHANGELOG.md: 变更记录
评审反馈:slots/sessions 与 inputTriggers 往往由不同 bundle 并发提供,
apply() 运行时一次性 ctx.get('inputTriggers') 可能 miss —— 带该服务的
宿主也会静默禁掉 /panel。改用 cordis 可选订阅:

  ctx.inject(['inputTriggers'], (scope) => { … })

服务任意到场顺序都能注册 /panel;缺失时仅不注册,渲染不受影响;
订阅 fiber 卸载时自动 dispose,无需塞进外层 disposers。

- src/client/index.tsx: /panel 注册 + 注释更新
- lib/client.js: 重建产物
- CHANGELOG.md: 注入修复条目同步描述订阅方式
@linshule
linshule force-pushed the fix/optional-inputtriggers-inject branch from c3ff436 to a5d4f4d Compare August 15, 2026 23:31
@linshule

Copy link
Copy Markdown
Author

收到,两点都已按要求完成 ✅

1. /panel 改为 cordis 可选订阅

按建议改成了 ctx.inject(['inputTriggers'], (scope) => …)(commit a5d4f4d):

  • 从硬注入列表移除 inputTriggers 保持不变(inject = ['slots','sessions']
  • apply() 内不再做一次性 ctx.get 查询,/panel 注册走可选订阅:服务任意到场顺序都能注册;缺失时仅不注册 /panel,渲染不受影响
  • 订阅 fiber 卸载自动 dispose,未塞外层 disposers

2. 两个分支均已 rebase 到最新 main

lib/client.js 已按各分支状态重建提交(tsc + tsdown 通过)。说明:两个 PR 都重建了 lib,合入顺序为 #10#11 时,第二个的 lib/client.js 合并可能需要最终重建一次(如需我可以在 #10 合入后立刻给 #11 重推)。

本地验证:jsdom 端到端(真实 shell MarkdownText + DOM 通道)在无 inputTriggers 的宿主上渲染 callout/chart/单组件根全部通过;测试套件新增回归全绿。辛苦 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants