前言¶
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 。