# dsh-selection-ask ![banner](assets/banner.png) [![npm](https://img.shields.io/npm/v/dsh-selection-ask.svg)](https://www.npmjs.com/package/dsh-selection-ask) [![npm downloads](https://img.shields.io/npm/dm/dsh-selection-ask.svg)](https://www.npmjs.com/package/dsh-selection-ask) [![license](https://img.shields.io/npm/l/dsh-selection-ask.svg)](LICENSE) [![node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-5FA04E?logo=nodedotjs)](https://nodejs.org/) [![stars](https://img.shields.io/github/stars/lzbaclz/dsh-selection-ask.svg)](https://github.com/lzbaclz/dsh-selection-ask) [![dsh plugin](https://img.shields.io/badge/dsh-plugin-4d6bfe)](https://github.com/deepseek-ai/deepseek-harness) [English](README.md) · [安装](#安装) · [使用方法](#使用方法) · [开发](#开发) · [故障排查](#故障排查) · [许可证](#许可证) **选中即引用,一键问 DeepSeek。** DeepSeek Harness 是一个以文本为先的终端 / agent 工作台,但「把上下文引用回对话」本该是一个手势,而不是复制粘贴的苦工。**dsh-selection-ask** 给 Web GUI 加上 ChatGPT 式的选区助手:在会话聊天流里**选中一段文字**,选区旁边就会浮出 **「询问 DeepSeek」** 按钮。点击它——选中文字即以 Markdown 引用(`> ` 前缀)写入输入框,输入框自动聚焦、光标停在末尾,直接打字追问即可。 ## 为什么选它 - **零摩擦引用** —— 你在 ChatGPT 里熟悉的那套手势,在 DeepSeek Harness 里原样拥有。 - **官方集成,不打补丁** —— 通过 harness 的 `inputActions.setDraft` 输入机 API 写入,撤销历史、草稿持久化全部照常;不碰 DSH 内部实现,不直接改 DOM 状态。 - **一条命令安装** —— 编译好的 `lib/` 已提交进仓库,从 GitHub 安装无需任何构建步骤。 ## 特性 - **按钮跟随选区** —— 用 `position: fixed` 定位到选区的屏幕坐标,自动贴边钳制,选区完全滚出视口后隐藏。 - **只引用聊天流里的文字** —— 选区必须落在聊天流容器(`[data-conversation-scroll]`)内;在输入框(composer)里选中文字不会触发(把草稿引用回草稿没有意义)。 - **引用感知的拼接** —— 多行选区逐行加 `> ` 前缀;草稿为空时直接替换,草稿非空时以空行隔开追加成独立段落。 - **会话级作用域** —— 切换会话自动重置按钮状态;提问 / 审批 composer 接管输入区时按钮随默认输入条一起隐藏。 ## 环境要求 - 带 `web` profile 的 DeepSeek Harness(`dsh web`),DSH 包版本为 `0.1.0-rc.6` 一代。 - 浏览器打开 harness GUI(默认 `http://127.0.0.1:3080`)。 - 从源码构建需要 Node `^22.19.0 || >=24` 和 pnpm;**直接安装不需要**(编译好的 `lib/` 已提交)。 ## 安装 > **环境要求**:DSH ≥ `0.1.5-rc.2`(`next` 通道 / DSH Desktop 桌面版)。 > > | 插件版本 | 适配的 DSH | > |---|---| > | `0.1.1` | DSH ≥ `0.1.5-rc.2` | > | `0.1.0` | DSH `0.1.0-rc.x`(`dsh-client-runtime` 时代) | ### 第 1 步:安装包 **从 GitHub 安装(推荐)** ```bash dsh plugin --profile web add github:lzbaclz/dsh-selection-ask ``` 想固定版本可指定 tag:`dsh plugin --profile web add github:lzbaclz/dsh-selection-ask#v0.1.0` **从本地克隆安装** ```bash git clone https://github.com/lzbaclz/dsh-selection-ask.git dsh plugin --profile web add link:/绝对路径/dsh-selection-ask ``` **从 npm 安装** *(尚未发布;发布后 `dsh plugin --profile web add dsh-selection-ask` 即可用,见[路线图](#路线图))* ### 第 2 步:激活 `dsh plugin add` 会把包自动登记进 profile 的 `dsh.profile.bundles` 列表,而本包自带 `cordis.patch.yml`,因此条目行会在**启动时自动插入**。下面两种激活路径**二选一,绝不要同时用**。 **路径 A —— 重启(零 YAML 编辑;新安装推荐)** 重启 `dsh web`(如果还没启动就正常启动)。完成——不需要改任何配置文件。 ```bash # 停掉你的 dsh web 进程,然后: dsh web ``` **路径 B —— 运行中的服务器热激活(不重启)** 直接用 `pnpm`(**不要用** `dsh plugin`,因为它会把包登记进 bundles),保持 bundles 列表不动,然后改 profile 的 patch 层——DSH 实时监听该文件并热应用: ```bash cd ~/.dsh/profiles/web pnpm add github:lzbaclz/dsh-selection-ask ``` 然后打开 `~/.dsh/profiles/web/cordis.patch.yml`,把 `[]` **替换**成 insert 块——文件必须保持合法的 YAML 列表: ```yaml # 改之前: # Your patch layer for this dsh profile, applied after every bundle layer: # a top-level YAML array of loader patch entries (id-targeted config # overrides, disables, and insert lists; `!!js` expressions allowed). [] # 改之后: # Your patch layer for this dsh profile, applied after every bundle layer: # a top-level YAML array of loader patch entries (id-targeted config # overrides, disables, and insert lists; `!!js` expressions allowed). - insert: - id: dsh-selection-ask name: dsh-selection-ask config: {} ``` 保存文件并**刷新浏览器页面**——插件已加载。 > ⚠️ **不要把路径 A 和路径 B 混用**(比如 `dsh plugin add` 之后又手动加 patch 行):条目会被注册两次,`dsh web` 启动失败,报 `duplicate loader entry id: dsh-selection-ask`。 > > ⚠️ **不要"追加"** YAML 块到已有的 `[]` 后面——那会变成非法文件,`dsh web` 启动失败,报 `failed to parse patches`。请按上面示范把 `[]` 替换掉。 > > 如果你的 profile 不叫 `web`,换成你的 profile 名;自定义了 harness 主目录的话,把 `~/.dsh` 换成你的 `$DSH_HOME`。 **旧版 profile**(由较早版本的 dsh 创建)可能缺少 `pnpm-workspace.yaml` 里的 `autoInstallPeers: false`。补上它,pnpm 就永远不会尝试自动安装预发布版本的 peer: ```yaml packages: - . nodeLinker: hoisted autoInstallPeers: false ``` ### 第 3 步:验证 刷新 GUI 页面,在聊天流里选中一句话——选区旁边应浮出 **「询问 DeepSeek」** 按钮。点击它,文字会以引用形式进入输入框。 ### 升级与热更新 ```sh dsh plugin --profile web add dsh-selection-ask@latest # 拉取新版本(桌面版:用应用内终端) ``` DSH 组合里常驻 `dsh-client-hmr` 链(会话实测:桌面版组合 `- id: client-hmr`,未禁用), 它**监听已安装插件 bundle 的变化并自动重载**——所以: - **升级已装插件**(同一条目、文件内容变化)→ 热更新,无需重启应用,页面通常自动生效 - **首次新增插件**(名册增加条目)→ 需要重启一次 DSH 服务 插件的样式、词典、组件注册都随 fiber 生命周期清理,热重载不会残留旧代码。 ## 使用方法 1. 在会话聊天流里选中任意文字。 2. 点击选区旁浮出的 **「询问 DeepSeek」** 按钮。 3. 选中文字被引用进输入框(形如 `> 选中的文字`),输入框已聚焦、光标在末尾——直接输入追问并发送。 ## 卸载 ```bash dsh plugin --profile web remove dsh-selection-ask ``` 如果你用的是路径 B,还要把 `cordis.patch.yml` 里的 `insert` 块删掉(还原成 `[]`),然后刷新 / 重启。 ## 故障排查 | 症状 | 原因与解法 | |---|---| | `dsh plugin add` 失败:`ERR_PNPM_NO_MATCHING_VERSION`,包名是 `@deepseek-ai/...` | 你的 profile 缺少 `pnpm-workspace.yaml` 里的 `autoInstallPeers: false`(旧版 profile),pnpm 尝试自动安装预发布版本(rc)的 peer 依赖而失败。补上这一行(见下文示例)、删掉 profile `package.json` dependencies 里多余的 `@deepseek-ai/*` 条目、删除 profile 的 `pnpm-lock.yaml`,然后重跑。本插件已把所有 peer 标记为 `optional`,因此永远不会触发自动安装。 | | `dsh web` 启动失败:`failed to parse patches` | insert 块被追加到了文件原有的 `[]` 后面,YAML 变成非法。把 `[]` 替换成 insert 块(见[路径 B](#第-2-步激活))。 | | `dsh web` 启动失败:`duplicate loader entry id: dsh-selection-ask` | 插件被注册了两次——你既用了 `dsh plugin add`(会加 bundle)又手动加了 patch 行。删掉其中一个。 | | 用 `dsh plugin add` 装好后刷新页面没反应 | bundles 列表的变更在启动时才生效——重启 `dsh web`(路径 A),或改用热激活的路径 B。 | | 选中文字后按钮始终不出现 | 选区不在聊天流内(试试选一条聊天消息的文字);或激活后没刷新页面;或 `insert` 行缺失。 | | 按钮出现但点击无效 | 浏览器缓存了旧 bundle——强制刷新页面(Cmd/Ctrl+Shift+R)。若本地重新构建过,先 `pnpm build` 再重新 `link:`。 | | 出现提问卡片时按钮消失 | 属预期行为:overlay 随默认输入条在接管状态下一起隐藏,接管结束后恢复。 | | `dsh plugin add` 失败 | 任何 pnpm 写法都支持(`link:`、`github:`、tarball URL)。检查 profile 路径是否在 `~/.dsh/profiles/` 下,以及 Node 版本。 | ## 开发 编译好的 `lib/` 已提交,正常安装永远不需要构建。想改代码: ```bash git clone https://github.com/lzbaclz/dsh-selection-ask.git cd dsh-selection-ask pnpm install pnpm typecheck # host + client 两段 tsc,零错误 pnpm build # tsc(host) + tsc(client) + tsdown 打包 pnpm verify # 离线冒烟:manifest/exports/patch/产物形状 pnpm test:dom # 无头 DOM 测试:jsdom 里挂载真实组件,断言选区→引用→写入草稿 # 对构建产物做离线冒烟校验 ``` - `src/index.ts` —— host 半(空 `apply`;它的存在是为了让服务器插件名册扫描到本包)。 - `src/client/index.tsx` —— 浏览器半:注入样式,注册进 `conversation.input.overlay` 插槽。 - `src/client/SelectionAskButton.tsx` —— 悬浮按钮组件:选区检测 / 定位 / 引用写入。 - `src/client/quote.ts` —— 纯逻辑 `buildQuote` / `appendQuote`(`verify` 离线测试的对象)。 - `src/client/styles.ts` —— 按钮样式 + HMR 安全的 `