Introduction¶
Running agents in DSH (DeepSeek Harness) usually results in long answers. When the answer ends, the page stays at the bottom: either manually scrolling up for a long time, or regenerating. The dsh-answer-top plugin solves this issue: after the AI finishes answering, it automatically scrolls the scroll container back to the first line of this round’s answer.
Below is an introduction to the plugin: who maintains it, what it does specifically after installation, how to install it, what switches can be configured, and in what scenarios not to rely on it.
What is this¶
dsh-answer-top is a client-side plugin for the DSH ecosystem, maintained by DSH-USER-GTB. It does not change DSH’s behavior; it only listens to the conversation DOM rendered by the DSH client and executes a scroll when detecting that “this round of answering has ended.”
One-sentence positioning: Answer ends → automatically scroll to the first line of this round’s answer, no need to scroll up every time.
It belongs to the category of client-side plugins: all logic is on the browser side (lib/client.js), the host-side lib/index.js is an empty plugin, and apply does nothing.
Core Features¶
Auto-rollback Trigger Conditions¶
The plugin listens to the DSH client’s DOM contract to determine if the current round of answering has ended:
MutationObserverlistens for changes to thedata-actions-revealattribute ondocument.body, with an 800ms timer as a fallback (to check missed events or session switches).- The appearance of
data-actions-revealon the[data-turn-tail]element indicates that this round of answering has ended. - Find the first node of this round with
[data-chat-flow-kind="assistant-step"]and scroll[data-conversation-scroll]to the top of it.
The DOM contract used is reverse-engineered from the DSH client:
[data-conversation-scroll] Scroll container
[data-chat-turn="N"] All flow nodes for round N
[data-chat-flow-kind] Node type: user / assistant-step / tool-call / turn-process
[data-turn-tail] Round end container
data-actions-reveal Attribute exists = round has ended
Behavior in various scenarios:
| Scenario | Behavior |
|---|---|
| Answer longer than one screen | Scroll to the start of the answer |
Answer shorter than max(viewport 25%, 80px) |
Do not scroll (avoids page jumping; threshold is adjustable) |
| Historical message loading | Do not scroll (only effective for the last round) |
| Turn number identification fails | Do not scroll (does not fallback to the entire answer to avoid accidentally scrolling to the start of the conversation) |
| During stream output | Do not scroll (waits until truly finished) |
| First entry into a session after startup | Jump to the start of the last round of answers (only once for the entire page lifecycle, repeated when switching sessions) |
Trigger Threshold (v0.12.0+)¶
The default behavior is “don’t scroll if the answer is short,” which is enforced by two rules taking the higher value: don’t scroll if the answer height is lower than max(viewport × 25%, 80px).
The threshold is adjusted in two sliders under DSH Settings → Left Navigation “Auto Rollback” → “Trigger Threshold”. Dragging applies changes immediately and saves them automatically:
| Control | Range | Default | Description |
|---|---|---|---|
| Minimum Answer Height | 5% – 100% of viewport | 25% | Do not scroll if the answer is lower than “viewport × this ratio” |
| Absolute Lower Limit | 0 – 200px | 80px | Regardless of window size, do not scroll if the answer is lower than this height |
Settings are persisted in the browser’s localStorage under the dsh-answer-top:config key, with the following structure:
{
"v": 1,
"enabled": true,
"sessions": {},
"threshold": { "ratio": 0.25, "px": 80 }
}
| Field | Description |
|---|---|
v |
Configuration version, still 1 |
enabled |
Master switch, default true |
sessions |
Reserved empty object, version 1 is always empty |
threshold.ratio |
Ratio of minimum answer height to viewport, 0.05 – 1, default 0.25 |
threshold.px |
Absolute lower limit in pixels, 0 – 200, default 80 |
Configurations written by old versions do not have a threshold field; when read, they are auto-filled with { ratio: 0.25, px: 80 } without overwriting the original storage, and only written back when the slider is dragged next time.
Master Switch¶
Enabled by default. After turning it off, it won’t scroll at all—not just a little less, but not at all. The switch is located in the “Auto Rollback” section of the DSH Settings page, but can also be executed in the browser console:
__dshAnswerTop.setEnabled(false) // Turn off
__dshAnswerTop.setEnabled(true) // Turn on
__dshAnswerTop.config() // View current configuration
Changes take effect immediately without refreshing. You can also change the threshold in the console, but it won’t be persisted:
__dshAnswerTop.setThreshold(0.15, 40)
Manual Rollback Button (v0.12.0+)¶
A small “⤒ Rollback” button always resides in the dock below the input box (conversation.composer.dock):
- Clicking immediately scrolls to the beginning of the last round of answers, even if auto-rollback is turned off or if a coexistence plugin conflict is detected;
- The manual path bypasses the master switch, the 300ms interaction window, and the height threshold;
- DOM marker:
data-dsh-answer-top-manual="1",title="Rollback to answer start".
You can also manually trigger it once via the console:
__dshAnswerTop.forceScroll()
Coexistence with Similar Plugins¶
If you enable other plugins that also “auto-scroll/jump” simultaneously, having two of them active will cause conflicts. The plugin reads the module list from ctx.modules (pure memory, no loopback, no network access):
// v0.12.0 onwards, only dsh-chat-answer-anchor is considered a conflict
["dsh-chat-answer-anchor"]
After a match, only the auto path is disabled: no automatic scroll when the answer ends, and the settings page shows “Detected dsh-chat-answer-anchor, auto-rollback paused.” The manual button and command line forceScroll() are not affected. Auto-resume occurs after disabling the anchor. The probe performs a lightweight re-check every 800ms; if ctx.modules is unavailable or malformed, the previous conclusion is kept—better to stay still than to make a mistake. dsh-jump-button is no longer considered a conflict since v0.12.0.
View probe results:
__dshAnswerTop.info().coexistence // { anchor, checked, probe }
__dshAnswerTop.diagnose().coexistence
__dshAnswerTop.moduleIds() // Verify if target plugin id has /client suffix
Data and Privacy¶
The plugin does not go online or upload data. The source code contains no fetch / XMLHttpRequest / WebSocket / sendBeacon, and it does not send data to DSH, GitHub, or third parties.
It only writes two keys to local storage locally: dsh-answer-top:config (settings) and dsh-answer-top:events (sanitized event logs, max 200 items / 50KB, oldest are dropped when exceeded). Event logs only record boolean or enum fields (errorType, at, v, count, anchor, probe, etc.), not message body, session id, user input, file paths, URLs, page content, screenshots, or raw error text. Cleanup methods:
localStorage.removeItem('dsh-answer-top:events') // Clear logs only
localStorage.removeItem('dsh-answer-top:config') // Reset settings along with logs
location.reload()
Installation and Enabling¶
The plugin is installed via a link string (link). After installation, you need to restart DSH (bundle-layer plugin). In DSH, go to Settings → Plugins → Add Plugin and enter the link installation string for the local directory:
link:<your-plugin-directory>
Equivalent command line syntax:
dsh plugin --profile desktop add link:<your-plugin-directory>
Uninstall:
dsh plugin --profile desktop remove dsh-answer-top
Download method: Community directory page https://www.skillhub.cn/plugins/DSH-USER-GTB/dsh-answer-top and source code repository https://github.com/DSH-USER-GTB/dsh-answer-top. For local development, use the same link command; just keep the directory unchanged when re-adding.
The version in package.json is 0.12.1, license MIT. As declared in the package, dsh.client.inject is [], but the runtime code’s exports.inject in the client side must be declared as ["slots", "modules"] to access ctx.modules, otherwise the coexistence probe is unavailable.
Typical Usage¶
- Install the plugin and start DSH.
- Open any session and send a request that triggers a long answer.
- After the answer ends, the page automatically returns to the first line of this round’s answer; if the answer is shorter than
max(viewport 25%, 80px), it stays at the end without moving. - To temporarily adjust the threshold, execute
__dshAnswerTop.setThreshold(0.15, 40)in the console (not persisted). - For long-term adjustment, drag the two sliders under “Auto Rollback → Trigger Threshold” in the settings page.
- When auto-rollback is paused by a coexistence plugin, click the “⤒ Rollback” button below the input box to jump manually.
Applicable Scenarios and Notes¶
Suitable for: DSH conversation scenarios dominated by long answers, especially for users who have to read from the beginning every time an answer ends; and for users who want manual control with just one permanent button. The settings page switch and manual button are two independent slots; the manual button is not affected when switching the primary/secondary position of the switch.
Things to note:
- The plugin runs with the current DSH process permissions; before installation, you should open the source code repository to verify the code and license (MIT).
- Only effective for the last round; it won’t scroll when browsing historical messages.
- Relies on DSH’s DOM contract. If it fails after upgrading DSH, it is likely because the attribute names changed—first confirm if the contract elements exist using the method below.
- Since v0.12.x, scrolling is an instantaneous jump (directly assigning
scrollTop), so smooth animations do not work on DSH’s new scroll containers.
Debugging¶
If it doesn’t work, verify the contract elements one by one in the browser console:
// Check if contract elements exist
document.querySelector('[data-conversation-scroll]')
document.querySelectorAll('[data-chat-turn]').length
document.querySelectorAll('[data-turn-tail]').length
// Check if the last round has ended
const tails = document.querySelectorAll('[data-turn-tail]');
tails[tails.length-1].hasAttribute('data-actions-reveal')
If the elements are there but it won’t scroll, check the console for errors. If the switch is not visible in the settings page, since v0.9.1, you can use one command to locate which layer the problem is in:
__dshAnswerTop.diagnose()
| Output | Meaning | Next Step |
|---|---|---|
injectCallbackRuns: 0 |
Slot hasn’t been declared, injection callback hasn’t run | Confirm settings.section still exists in DSH version |
injectCallbackRuns: 1, registerRuns: 0 |
Callback ran but registration failed | Check registerError |
slotEntryCount: 0 |
Registered but not in list | Check registerError / if abdicated |
slotEntryCount: 1, slotMounted: 0 |
Registered, just not the corresponding section open | Open Settings → “Auto Rollback” |
slotEntryCount: 1, slotMounted: 1 |
Everything normal | Check if component is hidden by CSS |
Note that teardown() unregisters the slot, and you must refresh the page afterwards to re-register.
Summary¶
dsh-answer-top is a single-purpose client-side plugin: auto-rollback, adjustable thresholds, manual button fallback, no internet access, and no sensitive data stored. Install and restart DSH to use; if DSH changes the DOM contract in the future and causes it to fail, first verify the existence of those contract elements in the console. Directory page: https://www.skillhub.cn/plugins/DSH-USER-GTB/dsh-answer-top ; Source code: https://github.com/DSH-USER-GTB/dsh-answer-top .