dsh-answer-top:AI 回答结束后自动滚到回答开头

前言

DSH (DeepSeek Harness) 里跑智能体,长回答是常态。回答结束时页面停留位置在末尾:要么从末尾往上手动翻半天,要么重新生成一次。dsh-answer-top 这个插件解决的就是这件事:AI 回答结束后,把滚动容器自动滚回到这一轮回答的第一行。

下面介绍这个插件:它是谁维护的、装上去之后具体做什么、怎么安装、有哪些开关可以配置,以及在什么场景下不要指望它。

这是什么

dsh-answer-top 是 DSH 生态的客户端插件,维护者是 DSH-USER-GTB。它不改变 DSH 的行为,只监听 DSH 客户端渲染出来的对话 DOM,在检测到「本轮回答结束」时执行一次滚动。

一句话定位:回答结束 → 自动滚到这一轮回答的第一行,不用每次往上翻。

它属于客户端插件:全部逻辑在浏览器侧(lib/client.js),宿主端 lib/index.js 是空插件,apply 不做任何事。

核心功能

自动回滚的触发条件

插件监听 DSH 客户端的 DOM 契约,判断本轮回答是否结束:

  • MutationObserver 监听 document.body 上 data-actions-reveal 属性的变化,另有 800ms 定时器兜底(漏事件或会话切换时补检)。
  • [data-turn-tail] 元素出现 data-actions-reveal,说明这一轮回答结束了。
  • 找出本轮第一个 [data-chat-flow-kind="assistant-step"] 节点,把 [data-conversation-scroll] 滚到它的顶部。

用到的 DOM 契约是从 DSH 客户端里逆向出来的:

[data-conversation-scroll]   滚动容器
[data-chat-turn="N"]         第 N 轮的所有流节点
[data-chat-flow-kind]        节点类型:user / assistant-step / tool-call / turn-process
[data-turn-tail]             轮次末尾容器
data-actions-reveal          该属性存在 = 本轮已结束

各情况下的行为:

情况 行为
回答比一屏长 滚到回答开头
回答短于 max(视口 25%, 80px) 不滚(避免页面乱跳;阈值可调)
历史消息加载 不滚(只对最后一轮生效)
turn 号识别失败 不滚(不 fallback 到全部回答,避免误滚到对话开头)
流式输出中 不滚(等真正结束才动)
启动后首次进入会话 跳到最后一轮回答开头(整个页面生命周期只一次,切会话不重复)

触发阈值(v0.12.0+)

默认行为是「回答短就不滚」,由两条规定同时生效、取更高一条:回答高度低于 max(视口 × 25%, 80px) 时不滚。

阈值在 DSH 设置 → 左侧导航「自动回滚」→「触发阈值」两个滑块里调,拖动立即生效并自动保存:

控件 范围 默认 说明
最小回答高度 视口的 5%~100% 25% 回答低于「视口 × 该比例」时不滚
绝对下限 0~200px 80px 无论窗口多大,回答低于这个高度都不滚

设置持久化在浏览器 localStorage 的 dsh-answer-top:config 键里,结构如下:

{
  "v": 1,
  "enabled": true,
  "sessions": {},
  "threshold": { "ratio": 0.25, "px": 80 }
}
字段 说明
v 配置版本,仍为 1
enabled 总开关,默认 true
sessions 预留空对象,首版恒空
threshold.ratio 最小回答高度占视口的比例,0.05~1,默认 0.25
threshold.px 绝对下限像素,0~200,默认 80

旧版本写出的配置没有 threshold 字段,读取时自动回填 { ratio: 0.25, px: 80 },不改写原存储,下次拖动滑块时才写回。

总开关

默认开启。关闭后完全不滚——不是滚少一点,是一次都不滚。开关放在 DSH 设置页「自动回滚」分区,也可以在浏览器控制台执行:

__dshAnswerTop.setEnabled(false)   // 关闭
__dshAnswerTop.setEnabled(true)    // 打开
__dshAnswerTop.config()            // 看当前配置

改了立即生效,不用刷新。控制台里改阈值也可以,但不持久化:

__dshAnswerTop.setThreshold(0.15, 40)

手动回滚按钮(v0.12.0+)

输入框下方的 dock(conversation.composer.dock)里常驻一个「⤒ 回滚」小按钮,始终显示:

  • 点击即滚到最后一轮回答开头,即使自动回滚关闭、或检测到共存插件冲突;
  • 手动路径跳过总开关、300ms 交互窗口和高度阈值;
  • DOM 标记:data-dsh-answer-top-manual="1",title="回滚到回答开头"。

也可以用控制台手动触发一次:

__dshAnswerTop.forceScroll()

与同类插件共存

如果同时启用了同样会「自动滚动/跳转」的插件,两个一起动会打架。插件读 ctx.modules 的模块名单(纯内存,不走 loopback、不联网):

// v0.12.0 起只把 dsh-chat-answer-anchor 视为冲突
["dsh-chat-answer-anchor"]

命中后只关自动路径:回答结束不再自动滚,设置页显示「检测到 dsh-chat-answer-anchor,已暂停自动回滚」。手动按钮和命令行 forceScroll() 不受影响。禁用 anchor 后会自动恢复。探测每 800ms 做一次轻量重检;ctx.modules 不可用或形状不符时保持上一次结论,宁可不动也不误伤。dsh-jump-button 自 v0.12.0 起不再算冲突。

看探测结果:

__dshAnswerTop.info().coexistence     // { anchor, checked, probe }
__dshAnswerTop.diagnose().coexistence
__dshAnswerTop.moduleIds()            // 核对目标插件 id 是否带 /client 后缀

数据与隐私

插件不联网、不上传。源码里没有任何 fetch / XMLHttpRequest / WebSocket / sendBeacon,不向 DSH、GitHub 或第三方发送数据。

只在本机 localStorage 写两个键:dsh-answer-top:config(设置)和 dsh-answer-top:events(脱敏事件日志,最多 200 条 / 50KB,超出丢最旧)。事件日志只记布尔或枚举字段(errorType、at、v、count、anchor、probe 等),不记消息正文、会话 id、用户输入、文件路径、URL、页面内容、截图和原始错误文本。清理方式:

localStorage.removeItem('dsh-answer-top:events')    // 只清日志
localStorage.removeItem('dsh-answer-top:config')    // 连设置一起重置
location.reload()

安装与启用

插件是链接安装串(link)形式,装完需要重启 DSH(bundle 层插件)。在 DSH 里进入 设置 → 插件 → 添加插件,输入本地目录的 link 安装串:

link:<你的插件目录>

命令行等价写法:

dsh plugin --profile desktop add link:<你的插件目录>

卸载:

dsh plugin --profile desktop remove dsh-answer-top

下载途径:社区目录页 https://www.skillhub.cn/plugins/DSH-USER-GTB/dsh-answer-top 和源码仓库 https://github.com/DSH-USER-GTB/dsh-answer-top。本地开发用同一条 link 命令,重新添加时保持目录不变即可。

package.json 里的版本是 0.12.1,许可证 MIT。按包内声明,dsh.client.inject 是 [],但 client 端运行时代码的 exports.inject 必须声明为 ["slots", "modules"] 才能拿到 ctx.modules,否则共存探测不可用。

典型用法

1、装好插件,启动 DSH。
2、打开任意会话,发一条会触发长回答的请求。
3、回答结束后,页面自动回到这一轮回答的第一行;如果回答短于 max(视口 25%, 80px),则停在末尾不动。
4、想临时调阈值,在控制台执行 __dshAnswerTop.setThreshold(0.15, 40)(不持久化)。
5、想长期调整,在设置页「自动回滚 → 触发阈值」里拖两个滑块。
6、自动回滚被共存插件暂停时,点输入框下方的「⤒ 回滚」按钮手动跳一次。

适用场景与注意

适合:以长回答为主的 DSH 对话场景,特别是每次回答结束都要从头读一遍的用户;以及想改成手动控制、只要一个常驻按钮的用户。设置页开关和手动按钮是两个独立槽位,开关切首选/备选位置时手动按钮不受影响。

需要注意:

  • 插件以当前 dsh 进程权限运行,安装前应打开源码仓库核对代码与许可证(MIT)。
  • 只对最后一轮生效,翻历史消息时不会跟着滚。
  • 依赖 DSH 的 DOM 契约。升级 DSH 后如果失效,多半是属性名变了——先按下面的方法确认契约元素是否存在。
  • v0.12.x 起滚动是瞬时跳转(直接赋值 scrollTop),在 DSH 的新滚动容器上 smooth 动画不生效。

调试

如果没生效,在浏览器控制台逐条确认契约元素:

// 看契约元素在不在
document.querySelector('[data-conversation-scroll]')
document.querySelectorAll('[data-chat-turn]').length
document.querySelectorAll('[data-turn-tail]').length

// 看最后一轮结束没
const tails = document.querySelectorAll('[data-turn-tail]');
tails[tails.length-1].hasAttribute('data-actions-reveal')

元素都在但不滚,看控制台报错。设置页里看不到开关时,v0.9.1 起可以用一条命令定位问题在哪一层:

__dshAnswerTop.diagnose()
输出 含义 下一步
injectCallbackRuns: 0 slot 还没被声明,注入回调没跑 确认 DSH 版本里 settings.section 仍存在
injectCallbackRuns: 1, registerRuns: 0 回调跑了但注册失败 看 registerError
slotEntryCount: 0 注册了但列表里没有 看 registerError / 是否被 abdicate
slotEntryCount: 1, slotMounted: 0 已注册,只是没打开对应分区 打开 设置 →「自动回滚」
slotEntryCount: 1, slotMounted: 1 一切正常 看组件是否被 CSS 隐藏

注意 teardown() 会注销 slot,之后必须刷新页面才会重新注册。

总结

dsh-answer-top 是一个功能单一的客户端插件:自动回滚、阈值可调、有手动按钮兜底、不联网不落敏感数据。装好重启 DSH 即可用;如果以后 DSH 改了 DOM 契约导致失效,先在控制台核对上面那几个契约元素是否存在。目录页:https://www.skillhub.cn/plugins/DSH-USER-GTB/dsh-answer-top ;源码:https://github.com/DSH-USER-GTB/dsh-answer-top 。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜