dsh-plugin-followup(划词追问)
在 DeepSeek Harness Web 界面里,选中助手回答中的任意文字 → 右键 → 追问。 追问的问答留在右侧面板里,主对话里对应的那一轮会被隐藏。
纯客户端插件(浏览器半区),不需要宿主侧服务。
解决什么问题(痛点)
在长回答里追问一个细节,本来是件小事,实际用起来却很别扭:
| 痛点 | 具体表现 | 本插件怎么做 |
|---|---|---|
| 一追问就要往回翻 | 回答很长,追问的问答越堆越多,想再看原来那一段就得往上滚半天,来回对照很累 | 选中的原文被钉成右侧卡片,对话滚到哪它都在眼前;同一张卡里继续追问,形成「引用 → 追问/回答 → 追问/回答」的线程 |
| 追问会把主对话冲乱 | 常规做法是把引用贴进输入框再发送,主对话里就多出一轮「引用 + 问题 + 回答」,阅读节奏被打断,上下文也越滚越长 | 追问产生的那一回合在主对话中被隐藏(按 data-chat-turn 精确到回合),需要对照时用面板底部开关随时放回来 |
| 引用要手工复制粘贴 | 想针对某个代码块、表格里的结论追问,得先选中、复制、粘贴,还要自己补引用格式 | 划词 → 右键 → 追问,> 原文 的引用格式自动生成;正文、代码块、宽表格、工具输出都能选 |
| 切工作区之后一片混乱 | 多个工作区/会话连着用,面板和记录混在一起,面板还会盖住主对话 | 面板住在原生右栏(宽度与比例由产品自己算,不覆盖对话),卡片按 sessionId 归属,每个工作区各自一份 |
| 上下文不能丢 | 如果追问另起一个"新会话",模型就失忆了,答非所问 | 追问走当前会话自身的输入通道(inputActions.setDraft() + submit()),上下文完整;主对话里的隐藏只是渲染层 |
⚠️ 关键区别:隐藏不是删除。会话日志原样保留(这正是上下文记忆还在的原因),你随时可以恢复;面板里的「清空」也会把隐藏的回合一并放回主对话。
演示
三步流程:选中 → 右键 → 问答留在右侧
追问面板结构
以上是按实际 UI 结构绘制的矢量示意图(
docs/flow.svg、docs/panel.svg),矢量图在任何缩放和深色背景阅读器下都清晰。 如果你想换成真实截图:把图片放进docs/,再把上面两行改成对应文件名即可。
功能
| 能力 | 说明 |
|---|---|
| 划词追问 | 在已完成的回答里选中文字 → 右键 → 追问(右侧面板);正文、代码块、表格、工具块都支持 |
| 右侧面板 | 面板住在原生右栏(接管 sidebar.right.tab.guide),宽度与比例由产品自己计算,不会盖住主对话 |
| 按会话独立 | 追问卡片挂在 sessionId 上,每个工作区/会话各自一份 |
| 卡片线程 | 每张卡片是一条线程:引用 → 追问1/回答1 → 追问2/回答2 …,底部输入框常驻,可继续追问 |
| 页签式窗口 | 每轮追问是面板顶部的一个页签(像浏览器标签):点击切换、× 关闭并恢复对应回合;同一页签里的继续追问留在同一页,新的划词追问自动新建页签并切过去 |
| 回答全文镜像 | 回答从 turn-tail 节点的 closing.blocks 读取(应用"复制"按钮同源),不截断 |
| 内置 Markdown 渲染 | 插件自带极简渲染器(零外部依赖):代码块围栏、行内代码、标题、列表、引用、表格、链接都按排版显示,不再露出原始的 ``` 围栏;卡片区另有错误边界,单个卡片渲染异常只退化出一行提示,不会拖黑整块面板 |
| 不打断阅读位置 | 发送追问时事前拦住应用对会话滚动容器的自动滚动,主对话连一下都不跳(另有 40ms 兜底校正);你一旦滚动 / 按键 / 点击应用立即解除,绝不抢占控制权 |
| 省空间的展示 | 引用/追问/回答过长时按高度折叠(不截断文本,避免把代码块切一半),可一键「展开全文(N 字)」 |
| 主对话隐藏 | 追问产生的那一回合按 data-chat-turn 在渲染层隐藏;会话日志不变,因此模型上下文完整保留 |
| 常驻入口 | 会话头部 追问 N 按钮,随时开关面板 |
| 可恢复 | 面板底部开关可把隐藏的回合放回主对话;「清空」为二次确认,并一并恢复 |
安装
插件包结构遵循 harness 的 Web 插件表约定:
dsh-plugin-followup/
├── package.json # dsh.client / dsh.bundle.patch 声明
├── cordis.patch.yml # 组合补丁:把插件插进 composition
├── dist/
│ ├── client.cjs # 浏览器半区(自包含,__ModuleLoader__ 契约)
│ └── index.js # 宿主半区占位(本插件不需要宿主逻辑)
├── README.md
└── LICENSE
dist/client.cjs 的外层契约:
window.__ModuleLoader__.load({
id: `dsh-plugin-followup`,
factory: (require) => {
const React = require(`react`)
const module = { exports: {} }
module.exports.name = `dsh-plugin-followup`
module.exports.inject = [`slots`, `timer`]
module.exports.apply = (ctx) => { /* 注册右键菜单、面板、镜像 */ }
return module.exports
},
})
安装方式
桌面版(推荐):本插件已提交到桌面端的内置预设列表(PR #544)。合并后,首次启动向导里会出现 Selection Follow-up (划词追问),勾选即自动安装;之后也可以在「设置 → 插件」里查看。
CLI / 自定义 profile:
dsh plugin --profile <profile> add github:MasterZ9286/dsh-plugin-followup
⚠️ 桌面端每次启动都会按它自己的清单重建 profile 的
package.json、dsh.profile.bundles与cordis.patch.yml,所以手改这几个文件会被覆盖 —— 请走上面的预设 /dsh plugin add路径。 ⚠️ 如果你同时在用动态 Cordis 插件版本(同一个会话里cordis_run起来的那份),请只留一个,否则会出现两个追问入口。
用法
- 在任意一条已说完的回答里选中文字
- 右键 →
追问(右侧面板) - 右侧出现该轮追问的页签:顶部是引用,中间是线程,底部输入框;再次划词追问会新建页签
- 写问题 →
Enter发送(Shift+Enter换行);在同一个页签里继续追问,问答会接着往下排 - 回答全文流入卡片;主对话里这一轮被隐藏
- 想对照原文时,点面板底部
主对话隐藏追问:开/关;「清空」会把隐藏的回合一并恢复
实现要点(都经过对运行时源码的核对)
- 抢占右键:应用自带的右键插件注册在
document捕获阶段,本插件注册在window捕获并调用stopPropagation(),因此在捕获路径上先于它执行;它开头的if (e.defaultPrevented) return会让它自行放弃,两层菜单不会叠加。 - 选区归属:以框架的
[data-slot="conversation.session"]限定"会话正文",而不是"最近的滚动容器"——后者会把自带滚动条的代码块/表格/工具块误判为区域外。取不到该属性时回退为旧的滚动容器启发式。 - 写回答:优先读会话快照
navigation.items()[i].response之外的全文来源,即turn-tail节点的closing.blocks(data-chat-turn用于定位回合)。 - 投递:使用插槽标准 props 的
inputActions.setDraft()+submit(),因此与主会话共享完整上下文;输入框已有草稿时只追加、不自动发送,避免覆盖用户正在写的内容。 - 自带 Markdown 渲染:面板不复用产品内部的
MarkdownText——它会动态加载 shiki / katex,在真实模块加载器里拿不到资源时会在渲染期抛错(v1.1.0 曾因此把面板整体拖黑)。现在改为插件自带的极简渲染器,覆盖回答里最常见的语法,且不引入任何外部依赖。dsh.client.external也相应移除。 - 错误边界:卡片区包在
PanelBoundary中,任何卡片渲染异常只退化出一行提示,不影响对话与面板其余部分。 - 折叠不改文本:只给容器加
max-height折叠(不截断源文本),因此 Markdown 结构不会被截断破坏。 - 阅读位置守卫(两层):① 事前拦截 —— 发送前在该滚动容器上临时屏蔽
scrollTop写入与scrollTo/scrollBy/scroll,应用"跟随到底部"的调用直接落空,主对话完全不动;② 事后兜底 —— 以 40ms 间隔校回原位(最长 120 秒),应对滚动锚定等其它路径。补丁只作用于那一个 DOM 元素,解除时按原样还原(含元素上原有的属性描述符)。解除时机:wheel/touchstart/ 翻页按键 / 在应用里打字 / 点击面板以外任意位置;面板自身的操作不解除。发送时若本来就贴在底部,则不介入。
已知限制
- 隐藏是渲染层的:会话日志没有任何改动(这正是上下文记忆保留的原因),所以"主对话干净"是视觉上的干净,不是删除记录。
- 面板接管了原生右栏的
guide("开始")标签页正文,插件运行期间该页面被替换;禁用/卸载插件即恢复。 - 首次追问会调用
ctx.layout.openRightbar(true, false)让中栏让出轨道;若窗口太窄导致无法保留轨道,则退回浮层模式(此时会覆盖一部分对话)。 - 依赖几个框架级 DOM 约定:
data-slot="conversation.session"、data-chat-turn、data-composer-input。上游结构变化时需要同步更新。
License
MIT
No comments yet. Be the first to write one.