为 DSH Web 端提供「划选对话文本→右侧面板提问」能力:在不打断主对话的前提下创建同工作区的独立追问会话,支持嵌套、上下文策略切换与追问记录树。
- 语言
- TypeScript
- License
- MIT
- 分支
- main
安装
$ dsh plugin --profile web add dsh-sidebar-qa在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程
对话式安装
帮我安装 DeepSeek Harness 插件 ChenRuoT/dsh-sidebar-qa:先查看仓库 https://github.com/ChenRuoT/dsh-sidebar-qa 确认安全性,然后执行安装命令并验证插件加载成功。
把这段指令粘贴给 DSH Web GUI 里的助手,由它代你完成安装与验证。
一句话定位
在 DSH Web 端的对话里划选任意一段文本,点击浮出的「提问」按钮,就能在右侧面板里得到一段完整问答——主对话不被打断,所有追问都跑在自动创建好的同工作区独立 DSH 会话里,可继续、可归档、可嵌套。
核心能力
- 对话中划选文本后浮出「提问」按钮,点击即在右侧面板打开追问 tab 并自动展开侧边栏(即使面板处于收起状态)
- 每次提问自动在同工作区创建一个独立 DSH 子会话(
❓追问·<主题>),主对话的 agent、消息流与队列全程不被触碰 - 三种上下文策略逐次可选:全量继承(fork 子会话命中 DeepSeek 前缀缓存,零压缩损失)/ 压缩(快速模型压旧历史 + 近期原文保留,默认)/ 机械裁切(最后 N 条消息原文直取,零 LLM 成本)
- 追问支持任意深度嵌套——在追问会话里再划选提问,可生成子追问
- 追问记录 tab 按根(主)会话分层树展示,限定当前工作区,可折叠、显示最近访问时间,归档/已删除的节点会置灰并标注状态,可一键从记录中移除
- 配置面板里的回答/摘要模型渠道与模型已改为下拉选择(来自运行时已配置的渠道),切换渠道自动联动模型,摘要渠道可选择「继承被追问会话」
技术实现
- 语言: TypeScript(ESM);前端 React 18 + 后端 Node 20+,同时为 host 和 client 两个 half 打包
- 关键依赖:
dsh-better-sidebar(硬 peer 依赖,提供ctx.betterSidebar注册表服务);@deepseek-ai/dsh-client-ui-primitives(图标);cordis(插件挂载框架);schemastery(设置 schema 校验) - 架构模式: 双半宿主插件(dual-half)。host half(
src/index.ts)注册/sidebarqa/api前缀路由,对外暴露config/catalog/config.get/config.update/context/title六个 JSON 方法,并通过ctx.inject(['settings'], ...)在sidebarqa命名空间下注册带 optimistic revision guard 的设置面板;client half(src/client/index.tsx)通过ctx.betterSidebar.registerTab注册两个 tab(追问 / 追问记录)并挂载一个document.body上的浮动「提问」按钮;所有跨半通信走标准webServer+sessionQuery+llm服务面 - 入口文件: host
src/index.ts(导出apply+inject = ['webServer','sessionQuery','llm','loader']),clientsrc/client/index.tsx(导出apply+inject = ['betterSidebar','sessions','connection','workspaces']),挂载通过cordis.patch.yml+package.json#dsh.bundle.patch同时声明 host/client 两半
适用场景
当你在 DSH Web 端与长对话 agent 协作时,常常需要对某一段回答追问细节——又不想被打断主线、复制粘贴到新会话又丢失上下文。本插件把「划选一段文字 → 右侧面板问答」做成一键操作:每个追问就是同工作区的独立 DSH 会话,可继续、可归档、可嵌套;上下文以「全量继承 / 压缩 / 机械裁切」三种策略按需切换,token 成本与答案还原度都掌握在你手里。
前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 0.1.0-rc.8 | 低于此版本无法解析 react / cordis / dsh-better-sidebar 的 peer 依赖;rc.7 及更早请先升级 DSH |
| dsh-better-sidebar | 0.14.0+ | 硬 peer 依赖;未安装时本插件保持不激活(无 UI、无行为、不创建会话) |
| Node | >=20 | 由 package.json#engines.node 声明 |
| @deepseek-ai/dsh-client-ui-primitives | ^0.1.0-rc.8 | 客户端 UI 图标与原语 |
| cordis | ^4.0.0-rc.8 | 插件运行时框架 |
| 平台 | 跨平台 | 纯 Web 浏览器插件,依赖 DSH Web 端运行,无原生模块 |
安装方式
dsh plugin --profile web add github:ChenRuoT/dsh-sidebar-qa
配置项
所有配置经 DSH 设置服务 sidebarqa 命名空间下发,写入走 /sidebarqa/api/config.update 带 revision 乐观锁(多窗口冲突时提示重试)。Web 入口在「DSH 设置 → 侧边卡片 → 追问卡片右上角齿轮 → 功能配置」弹窗中,下表只列出面板暴露的 8 项常用设置;其余内部调参键(summarizeBudgetTokens、recentWindowMessages、backgroundWindowMessages、titleBudgetTokens)可在 settings.yaml 的 sidebarqa 命名空间里直接配置。
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
historyStrategy | 枚举 | 新追问默认上下文策略:inherit 全量继承(fork + 缓存命中)/ compressed 压缩 / trim 机械裁切;面板内可逐次切换 | compressed |
trimWindowMessages | 数字 (1-256) | 机械裁切模式保留的最近消息条数 | 10 |
summarizeProvider | 字符串 | 摘要快速模型渠道;留空 = 继承被追问会话的 provider | '' |
summarizeModel | 字符串 | 摘要快速无思考模型 | deepseek-v4-flash |
summarizeReasoningEffort | 枚举 | 摘要思考模式 off / high / max | off |
answerProvider | 字符串 | 子对话回答模型渠道 | deepseek-official |
answerModel | 字符串 | 子对话回答模型 | deepseek-v4-flash |
answerReasoningEffort | 枚举 | 子对话思考模式 off / high / max | off |
常见问题
Q: 必须先安装 dsh-better-sidebar 吗?
A: 必须。dsh-better-sidebar 是硬性 peer 依赖,未安装时 client 端 inject = ['betterSidebar', ...] 拿不到服务,本插件保持不激活——没有任何 UI、行为,也不会创建会话。先执行 dsh plugin --profile web add dsh-better-sidebar@latest,并要求 0.14.0+。
Q: 划选文本后点「提问」,主对话会被打断吗?
A: 不会。host 端只读取主会话的当前模型表面(ctx.sessionQuery.readSurface,不打开发送队列),client 端新建一个独立的 DSH 子会话承担问答;主对话的 agent、消息流与队列全程不会被触碰。
Q: 三种上下文策略我该怎么选?
A: 「全量继承」从主会话最新已完成 turn 分叉子会话,完整历史作为种子继承,可命中 DeepSeek 前缀缓存但必须沿用主会话模型;「压缩」用快速无思考模型压旧历史 + 近期原文保留(默认,省 token);「机械裁切」直接取最后 N 条消息原文,零 LLM 成本。发起新追问时在面板左侧的策略 chip 逐次切换。全量继承在主对话正在回答时(尚无已完成 turn)会自动降级为压缩。
Q: 追问会话的标题为什么先是占位,答完才更新?
A: 这是两段式命名:创建时用引文首行做占位标题(❓追问·<首行>),首次回答完成后由 host 端用快速无思考模型从「问题+回答」提炼 ≤15 字最终标题并 rename 覆盖。提炼失败时保留占位,不会再重试。
Q: 追问记录中的「已归档 / 已删除」置灰条目是什么?点「移除」会删掉 DSH 会话吗?
A: 你自行在 DSH 端归档或删除的会话,本插件的 localStorage 父→子映射不会自动清理,记录 tab 会把它们置灰并标注状态。点「移除」只清理本插件的映射与衍生状态(titled / collapsed),不触碰 DSH 侧的真实会话。
Q: 关闭浏览器后再回来,追问记录还在吗?
A: 在。父→子映射、已命名标记、追问记录树的折叠状态都持久化在 localStorage 的 dsh-sidebar-qa:map / :titled / :collapsed 三个键里。DSH 侧真正的会话由其自身的会话存储独立保留。
Q: 修改后端后必须重启 dsh web 吗?
A: host 半改动(/sidebarqa/api 摘要、标题、设置命名空间)需要重启 dsh web;client 半改动(划选浮层、追问/追问记录 tab、模型座、配置面板)只硬刷新浏览器即可,不必重启服务。
Q: 选区超过了单条消息或包含正在流式输出的文本,能划选提问吗?
A: 不能。划选会被识别为无效:必须落在同一 [data-chat-anchor-key] 节点内、且该节点未处于 data-streaming 状态、被流式输出折叠回单条消息后才能划选。单次选区文本长度上限 2000 字符。这些校验在 src/client/selection.ts 的 captureSelection 里完成。
上手难度
入门 — 只要装好 dsh-better-sidebar,划选文本点「提问」即可;进阶部分(策略切换、模型下拉、追问记录树)按需探索,无需阅读源码。
已知问题与限制
- DSH 上游限制:
sessions.selectModel会无条件把所选模型同时持久化为全局默认模型。因此压缩/裁切追问在给子会话设置模型时,仍会改变「新建会话的起始模型」;一个从未发过请求的会话也会从该全局默认解析自己的当前模型,可能看起来跟着变。插件侧无 API 可规避(selectModel无 opt-out、不写会话日志,sessions.models只能拉取),见 CHANGELOG 0.3.2。 - 划选有 4 道硬门槛:必须落在单条消息
[data-chat-anchor-key]节点内、目标消息未在流式输出、选区非空且 ≤2000 字符;任一不满足浮层不会弹出(src/client/selection.ts:9-63)。 - 选区范围限定浏览器原生
window.getSelection(),无法跨 iframe / Shadow DOM 划选;这些区域内的文本不会触发浮层。 - 全量继承策略在主对话「正在回答、尚无已完成 turn」时自动降级为压缩,面板会提示;fork 失败的其他原因(如网络)同样降级为压缩,不会阻塞提问。
- 父→子映射、titled 标记、折叠状态全部存在 localStorage:清除浏览器站点数据会丢失这些元数据,但 DSH 侧的追问会话本身不受影响。
- 浮层仅监听
document上的selectionchange/mouseup/keyup;当 DSH Web 端以非标准方式渲染对话内容(例如自定义容器未携带data-chat-anchor-key/data-chat-flow-kind标记)时,划选不会被识别为有效对话文本。
划选提问 上下文摘要 嵌套追问 追问记录 零打断DeepSeek Harness(DSH)Web 插件:在对话里划选任意文本 → 点击「提问」→ 右侧面板问答——
自动创建同工作区的独立 DSH 会话,主对话零打断。实现类 codex 侧边提问 / Claude Code `/btw` 功能。
✨ 功能一览
- 📝 划选提问:对话中划选任意文本 → 浮层「提问」→ 右侧面板内嵌问答,全程不跳转大窗口;侧边栏面板收起时也会自动展开,「提问」永远有可见反馈
- 🧠 智能摘要:快速无思考模型把主对话上下文压缩成小摘要,与划选引文一起注入首条消息
- 🔀 三种上下文策略:每次提问可在「全量继承(fork+缓存命中)/ 压缩 / 机械裁切」间切换,面板内选择器 + 配置默认值双入口
- 🔗 独立会话:自动创建同工作区独立 DSH 会话(
❓追问·<主题>),可继续、可归档,主对话零打断 - 🪆 嵌套追问:在追问对话里再划选提问,生成子追问,层层嵌套
- 🗂️ 追问记录:按根(主)会话分层树展示;限定当前工作区;节点可折叠、显示最近访问时间;点击跳转后追问记录 tab 保持开启;已归档/已删除的追问置灰标记状态,可一键从记录中移除(连同整棵子树清理映射,不影响 DSH 侧会话)
- 🏷️ 两段式命名:划选首行占位命名 → 首次回答完成后基于「问题 + 回答」自动提炼 ≤15 字最终标题
- ⚙️ 可配置:摘要/回答模型渠道、思考模式、上下文窗口与预算全部可调(设置页齿轮弹窗)
🔌 基于 dsh-better-sidebar 开发的第三方拓展 Tab,通过
ctx.betterSidebar.registerTab注册;能力对等内置 tab,安装即用。
前置依赖(必装)
dsh-better-sidebar 必须安装(未安装时本插件不激活,无任何 UI/行为,也不创建会话),且需 0.14.0+(对应 DSH 0.1.0-rc.8;rc.7 及更早的 DSH 环境无法解析本插件的 peer 依赖,请先升级 DSH)。
dsh plugin --profile web add dsh-better-sidebar@latest
安装
# 通过 npm(推荐)
dsh plugin --profile web add dsh-sidebar-qa
# 或本地路径
dsh plugin --profile web add <本仓库路径>
重启 dsh web(host 半改动需要重启;client 改动浏览器硬刷新即可)。
使用
- 在任意对话(主对话或追问对话)中划选一段文本,点击浮层「提问」。即使右侧面板处于收起状态也会自动展开(对应 issue #6),「追问」tab 直接可见——包括"先手动收起面板、再点提问"的重复场景。
- 右侧「追问」面板变成一条内嵌对话:引文/问题在侧边栏内流式回答,输入框固定在下方面板底部,不会跳转到子对话大窗口。
- 回答过程中可在输入框继续追问(Enter 发送、Shift+Enter 换行),所有问答都在侧边栏内完成。面板底部的输入框复用 DSH 主对话的输入栏外观(同一套设计 token 的圆角胶囊卡片):发起新追问时左侧是上下文策略 chip,右侧的模型选择(与主对话同一份
session.models/selectModel数据,切换互通)与 context 占用环(复用contextPressure投影)始终可见——新追问时它们绑定被追问的父会话(context 环即父会话占用,可据此判断用全量还是裁切;在面板切换模型会立即改父会话模型,全量继承的子会话天然沿用它,压缩/裁切子会话则使用你选的模型),继续已有追问时绑定该追问会话;最右侧为上箭头发送键。 - 每个追问仍是同工作区的独立会话(
❓追问·<主题>),主对话零打断;追问可以嵌套(在追问对话里再划选提问会生成新的子追问)。发起新追问时,输入框左侧的上下文策略 chip 可选择策略(默认取配置historyStrategy):- 全量继承:
sessions.fork从主会话最近的已完成 turn 分叉子会话,完整历史随种子继承,首条请求复用主会话消息前缀 → DeepSeek 自动前缀缓存命中、零压缩损失;子会话沿用主会话模型。主对话正在回答(无已完成 turn)时 fork 自动降级为「压缩」并提示。追问 tab 中,继承的父对话历史显示在分割条上方,默认视图锚定在本追问自己的「引用 + 提问」处,向上滚动分页加载父对话历史(与主对话「加载更早」体验一致)。 - 压缩:快速模型压缩较早窗口 + 近期原文保留(默认,省 token)。
- 机械裁切:最后
trimWindowMessages条消息原文直取,零 LLM 成本、确定性输出。
- 全量继承:
- 侧边栏「追问记录」tab 按根(主)会话分组,以分层树列出当前工作区内的所有(嵌套)追问(归属判定:当前会话所在工作区,见
src/client/history-scope.ts),点击跳转。有子追问的节点右侧有折叠按钮(箭头随折叠状态旋转)收纳子树,其左侧显示该对话组最近访问时间(相对标签,复用 DSH 左侧面板的样式与数据源sessions.list.updatedAt)。跳转后目标会话的追问记录 tab 保持开启(定向openTab(seed, scope),已打开则聚焦、未打开则新建)。被归档或删除的追问(用户自行管理会话时)会置灰并标注「已归档 / 已删除」,不可再点击跳转,行尾的「移除」按钮将其从记录中清除(连同整棵子树清理 localStorage 映射,DSH 侧会话本身不受影响)。
配置
配置走 DSH 设置服务 sidebarqa 命名空间(settings.yaml 或设置页)。Web 界面入口:DSH 设置 → 侧边卡片 → 「追问」卡片右上角的齿轮「功能配置」弹窗(由 dsh-better-sidebar v0.12+ 的 settings.render 提供),可逐项编辑下表字段——文本行 blur/Enter 提交,数字行按区间钳制,写入经 /sidebarqa/api/config.update 带 revision 乐观锁(多窗口冲突时提示重试)。回答/摘要的模型渠道与模型为下拉框,选项来自运行时已配置的渠道。
| 键 | 默认 | 说明 |
|---|---|---|
historyStrategy | compressed | 默认上下文策略:inherit 全量继承(fork+缓存命中)/ compressed 压缩 / trim 机械裁切(面板内可逐次切换) |
trimWindowMessages | 10 | 机械裁切模式保留的最近消息条数(1–256) |
summarizeProvider | '' | 摘要快速模型渠道;空 = 继承被追问会话的 provider |
summarizeModel | deepseek-v4-flash | 摘要快速无思考模型 |
summarizeReasoningEffort | off | 摘要思考模式(off/high/max 三档下拉) |
answerProvider | deepseek-official | 子对话回答模型渠道 |
answerModel | deepseek-v4-flash | 子对话回答模型 |
answerReasoningEffort | off | 子对话思考模式(off/high/max 三档下拉) |
面板只展示上述 8 项常用设置;压缩/标题的内部调参键(
summarizeBudgetTokens、recentWindowMessages、backgroundWindowMessages、titleBudgetTokens)不在面板暴露,仍可在settings.yaml的sidebarqa命名空间里配置。
压缩模式的下上文注入刻意保持轻量:旧背景压成最多 3 句话(目标 / 当前进度 / 未决事项),近期只保留最近 2 条且每段强截断(≤400 字符);模型侧从新到旧提交,让当前进度落在注意力最强位置。摘要失败/无渠道时自动降级为「仅近期对话 + 引文 + 问题」,问答不中断;全量继承失败(主对话正在回答)时自动降级为压缩模式。
架构
dsh-sidebar-qa (bundle: dsh.bundle + package.json#dsh.client)
├── src/index.ts host:/sidebarqa/api 摘要 + 标题服务 + sidebarqa 设置命名空间
├── src/summarize.ts 表面文本抽取 + 流组装(纯函数,可测)
├── src/title.ts 标题提示词 + 规范化 + Q+A 输入框定(纯函数,可测)
├── src/config.ts 设置 schema + 默认值
├── src/context-types.ts 结构化 cordis 服务面 + Context 增补
└── src/client/ 浏览器:选区捕获、浮层、问答面板、会话编排、追问记录
├── index.tsx apply:注册 2 个 better-sidebar tab + 浮层
├── selection.ts 选区捕获与校验(单消息/非流式/≤2000 字符)
├── SelectionPopover.tsx 划选浮层「提问」按钮
├── AskPanel.tsx 追问 tab(内嵌对话:流式 transcript + DSH 风格输入卡片 + 追问切换)
├── HistoryPanel.tsx 追问记录 tab(分层树:折叠按钮 + 最近访问时间 + 工作区限定 + 归档/删除置灰与移除)
├── history-scope.ts 工作区归属解析 + 树过滤 + 子树最近访问时间 + 会话状态判定(live/archived/gone)与子树移除(纯函数,可测)
├── history-time.ts 相对时间分桶 + 中文标签(纯函数,可测,复用左侧面板样式)
├── StrategySelect.tsx 上下文策略 chip(PermissionSelect 同款触发器 + Menu)
├── ModelSelect.tsx 模型选择(session.models/selectModel 驱动的双层菜单,与主对话互通)
├── model-menu.ts 模型目录扁平化/选中解析(纯函数,可测)
├── ContextMeter.tsx context 占用环(contextPressure 投影 + breakdown 面板)
├── context-meter.ts 占用百分比/紧凑 token 格式化(纯函数,可测)
├── ensure-panel.ts 面板收起自愈:展开判定 + 经 SidebarStore 展开(纯函数,可测)
├── tab-activation.ts onActivate 激活桥:收起后重新激活 tab 时再次自愈(issue #6)
├── orchestrate.ts create → 占位 rename → selectModel(默认 flash/关思考) → prompt + 继续追问 + 回答后重命名
├── ConfigPanel.tsx 功能配置面板(设置齿轮弹窗:编辑 sidebarqa 命名空间,回答/摘要模型渠道与模型下拉)
├── config-fields.ts 配置面板行声明 + 数字钳制 + catalog 选项解析(纯函数,可测)
├── store.ts 父→子 映射(localStorage 持久化,支持嵌套)+ 待提问引文 + 已命名标记
├── injection.ts XML 转义/消毒 + 注入格式 + 占位主题生成
├── answer.ts 历史流 → 回答文本折叠
└── api.ts /sidebarqa/api fetch 封装 + 当前模型读取
跨插件 seam(meta.quote)
外部插件可以打开追问 tab 并预填引文(不经本插件的划选浮层):通过 better-sidebar 的 openTab seed 携带 meta:
ctx.betterSidebar.openTab(
{ type: 'dsh-sidebar-qa:ask', meta: { quote: { text: '选中的内容', role: 'user' } } },
)
面板优先显示 meta.quote(形状校验:text 为非空字符串;可选透传 role / messageId),回退到本插件浮层的 pending 引文;用户输入问题发送后引文即被消费(updateTab 清除),刷新/再次聚焦不会复现旧引文。<quoted_context> 的 source 标签沿用 agent-history。
关键数据流
划选文本 ─▶ 浮层[提问] ─▶ 右侧面板(引文 + 底部输入框)
回车 ─▶ ① host 摘要:sessionQuery.readSurface(被追问会话) → llm 快速无思考模型压缩
② client 创建会话 sessions.create(workspaceId)
③ rename → "❓追问·<划选文本首行占位>"
④ selectModel(默认 deepseek-v4-flash, 思考关闭)
⑤ prompt(摘要块 + <quoted_context> + 问题)
─▶ 面板轮询 sessions.history 流式渲染 transcript(不跳转大窗口)
─▶ 首次 turn/end 后 ⑥ host 标题:Q+A 截断 → llm 快速无思考模型提炼 ≤15 字主题
→ rename 覆盖为 "❓追问·<最终主题>"(仅一次,失败保留占位)
─▶ 底部输入框继续追问;主对话零影响;追问可嵌套
上下文注入格式(首条消息)
<统领性指令:这是「侧边栏追问」,只围绕划选文本主题直接回答……>
【主对话上下文】
【背景】<模型压缩的旧历史,最多 3 句话>
【近期对话】<最近 2 条近原文,每条 ≤400 字符>
<quoted_context source="agent-history" label="Agent 回复"
message_id="<id>" role="assistant" turn="<n>">
<引文原文>
</quoted_context>
问题:<用户输入>
统领性指令置于输入最前,利用注意力机制让模型先定调「聚焦划选文本」再读上下文;用户问题虽然在输入末尾,但划选文本(quoted_context)与指令共同锚定了回答范围。追问会话内的后续消息默认不带主对话上下文(只有首条携带)。
构建与测试
pnpm install
pnpm build # tsc 声明 + tsdown(lib/index.js + lib/client.js + lib/client-registry.js)
pnpm test # vitest 单测(injection / summarize / answer / store / title / meta-quote / history-scope / history-time / model-menu / context-meter / config / ensure-panel / tab-activation)
pnpm typecheck
License
MIT
查看使用指南 →
该插件的安装步骤、关键要点、FAQ 与兼容性说明(基于已收录字段派生)。
收录徽章
[](https://deepseek-plugin.org/plugins/ChenRuoT/dsh-sidebar-qa)把这段 markdown 粘贴到你的 GitHub README,链接回本插件详情页。徽章只声明已被本站收录,不代表安全认证。