dsh-skill-picker 0.5.25 → 0.5.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/client.js +328 -260
- package/lib/client.js.map +2 -2
- package/package.json +1 -1
- package/src/client/index.jsx +357 -133
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ DSH Web GUI 的技能选择器:在输入框(composer)工具行右侧加一
|
|
|
26
26
|
|
|
27
27
|
English: A skill picker for the DSH Web GUI — a button in the composer's right tool row opens a searchable list of installed skills; picking one inserts the official `/skill-name` gesture into the draft, so DSH's native user-invocation path loads the skill with your message.
|
|
28
28
|
|
|
29
|
-
当前版本:**v0.5.
|
|
29
|
+
当前版本:**v0.5.26**(**⚡ 面板可管理技能**:右键/长按 → 置顶 · 关闭(`SKILL.md`→`SKILL.md.disabled`,agent 也不再加载)· 定位 · 卸载(移进备份目录,从不删除,可撤回);「已关闭」收成右下角一个入口;**光标落回输入框末尾**;**右键菜单跟随主题且实心**;**面板字体与 DSH 统一**(`--dsw-font-family`,分组小标题对齐侧栏规格);**零 emoji**;提示条自动消失;搜索框无边框 32px + **面板带 `data-dsh-skill-picker` 标记**(供 dsh-pocket 手机端放行,见 `local/dsh-pocket-mobile-fix/skill-panel-fileguard-fix-2026-10-02.md`)+ **「置顶 / 最近使用」在桌面端、网页端和手机之间共用同一份**(存于 `$DSH_HOME/dsh-skill-picker-state.json`,不再各存各的 localStorage)+ **`/` 菜单与 ⚡ 面板排序完全一致**(空查询也走同一套排序,置顶排最前)+ **修掉「打 `/` 菜单开到下面去」**(microtask 竞态:技能组必须 0 个 `await` 才抢得到高亮)+ **文件补丁退休**(`/` 增强改为运行时接管,默认不再改任何官方文件,并会自动把改过的还原)+ 模糊/拼音匹配 + ⚡ 面板置顶分组 + 搜索结果按匹配相关度排序)
|
|
30
30
|
|
|
31
31
|
## 为什么用它(vs 官方 `/` 补全)
|
|
32
32
|
|
|
@@ -141,6 +141,7 @@ source.onPick = (args) => { track(args?.candidate?.name); return onPick.call(sou
|
|
|
141
141
|
|
|
142
142
|
## 更新日志
|
|
143
143
|
|
|
144
|
+
- **v0.5.26**:**四个细节修复 + 八个追加修复(全部是国豪实机挑出来的)**:① 选完技能光标**先是不回来**、② 回来了但**落在开头**、③ 右键菜单在浅色主题下**看不清**、④ 深色主题下**半透明**;追加 ⑤ 提示条**不再常驻**、⑥「已关闭」**改成右下角一个入口**、⑦**字体统一到 DSH 自己的 UI 字体**、⑧**分组小标题对齐 DSH 规格 + 去掉 emoji**、⑨**修「已关闭的技能还出现在主列表里」**、⑩**行内那个置顶针也删掉**(改走右键菜单,至此面板零 emoji)、⑪**底部那句提示改成一行短的并永久保留**、⑫**搜索框改成"模型选择器那个"样式(无边框)**、⑬**面板加 `data-dsh-skill-picker` 标记**(配合 `dsh-pocket` 的手机端"文件守卫"放行 —— 那是个独立插件的 bug,详见 `local/dsh-pocket-mobile-fix/skill-panel-fileguard-fix-2026-10-02.md`)。**① 光标不回输入框**:他的原话 `点了按钮然后选了 skill,skill 的名字确实落到聊天框里了,但是没有光标闪烁,意味着我还要点一下聊天框才能继续打字;但如果你是通过输入斜杠、在出来的列表中点击 skill,那它是有闪烁光标的`。**根因**:点技能行时**焦点落在了那个按钮上**,随后面板关闭、按钮卸载 → **焦点掉回 `body`**,于是输入框有内容但没光标;官方 `/` 菜单没这问题,因为整个过程焦点一直在 composer 里。**修法**:`pick()` 之后用一个版本无关的 DOM 兜底把焦点还回去 —— 从 ⚡ 自己的节点**向上最多 8 层**找到包含编辑器的祖先并 `focus()`;放在 `requestAnimationFrame` 里执行,等面板卸载 + React flush 完。**② 光标落到了开头**:他紧接着截图指出 `/coding` 的光标在**斜杠前面**。**根因**:给 `contenteditable` 调 `focus()` 而**不给选区时,规范就是把光标放开头** —— 我上一版正是只 focus、没摆选区。**修法**:显式把 Range **塌缩到末尾**(等价于"鼠标点在这行末尾",官方编辑器会同步 DOM 选区),并在 **60ms 后带守卫地再补摆一次**(只在输入框仍聚焦时才动,免得覆盖用户刚点的别处)—— 防的是富文本编辑器 focus 后又把自己的选区重置回去。⚠️ **再调一次 `setDraft` 是没用的**:文字没变它会直接 return,光标永远不动。**实测**:光标位置 **15 / 15**(= 文字长度)→ 末尾 ✅;**直接键入 `XYZ`** → `/backup-memory XYZ` ✅。**③④ 菜单配色(改了三次才对,过程值得记)**:他先在网页端(浅色主题)发现**字看不清**,再在桌面端(深色主题)发现**背景是半透明的**。**第一次错**:背景用了 `--dsw-alias-bg-elevated` + 深色兜底 —— 实测这个变量**在 DSH 里根本不存在**,所以深色兜底永远生效,而文字用的是**存在且跟随主题**的 `--dsw-alias-label-primary` → 浅色主题下 **深底深字,对比度实测 1.25**。**第二次错**:改用官方语义变量 `--dsw-specific-menu` —— 它存在,但在深色下是 **`#43454a73`(alpha 0.45)**,那是**配合 DSH 自己的背景模糊**用的;我没加模糊,于是对话内容直接透过菜单 ✓ 被他抓个正着(他的截图给了决定性证据:**同一屏上面板实心、菜单半透明**)。**最终修法**:背景改用**面板自己用的那个变量** `--dsw-specific-tip`(两个主题都是实心 ✓),并加 `backdropFilter: blur(8px)` 当保险;文字/悬停仍走主题变量。**实测(这次专门量"透明度"这个属性本身,因为前两轮就是没量它才来回翻车)**:浅色 → 面板与菜单**同为** `rgb(245,246,247)`、**alpha=1**、对比度 17.46;深色 → **同为** `rgb(53,54,56)`、**alpha=1**、对比度 11.57 ✅。**顺带**:右键菜单之前**完全没有 hover 反馈**,这次补上了(和面板行同一套做法)。**⑤ 提示条常驻不消失**:他问 `这个消息停留得也太久了吧,多少秒啊你` —— 提示条是 toast,本该自己消失,我漏了超时。**修法**:普通成功提示 **2.6 秒**自动消失;**带「撤回」的(卸载)给 10 秒**,因为撤销它等于撤掉唯一一条退路;面板重新打开时清空(上次的提示已经过期)。**验证教训**:我自己的探针先报"10.27 秒"、又报"产物里没这个数字",**两次都是我的检查写错了**(探针在提示条消失后误抓到了「⏻ 已关闭」分组标题;检查条件写死 `10000` 而 esbuild 把它印成了 `1e4`)—— **他说"我测了其实对的",是他对的。先怀疑自己的量具,再怀疑代码。** **⑥「已关闭」从常驻分组改成右下角一个入口**:他的原话是 `它应该跟我红方平齐,放在靠右下角的位置` + `点击后里面显示的都是所有已关闭的 skill` + `在已关闭的 skill 列表中右键可以像刚刚一样弹出列表` + `不然你这个"已关闭"是不是有点太占地方了,它一直显示的话`。**修法**:底部那行改成 `justify-content: space-between` —— 左边是操作提示(切到已关闭视图时提示也会随之变成「右键 = 开启 / 定位」),右边是一个**小胶囊** `⏻ 已关闭 N`;点它**整个列表切换**成已关闭视图(搜索框同样能过滤它,走同一个 `matchRank`),再点一次变 `← 全部技能` 切回来;视图里**右键/长按**弹同一个菜单(对已关闭的技能只给「开启 / 定位」,不给「置顶 / 卸载」)。面板每次打开**重置回全部视图**(免得它停在上次的视角里)。顺带删掉了没人用的 `actionSpanStyle`。**实测(真浏览器,用一个自己建的假技能走完整条路)**:全部视图 → 胶囊显示 `⏻ 已关闭 1` 且**右对齐** ✓、已关闭的技能**不在主列表里** ✓;点胶囊 → `all=false / listsClosed=true / listsNormal=false`、提示变成「右键 = 开启 / 定位(手机长按)」✓;右键 → 菜单**恰好**是 `/zz-view-selftest`、`⏻ 开启`、`📂 在文件管理器中定位` ✓;点开启 → 该行消失 ✓。**测试后验证过磁盘**:只剩他自己 8-13 关的 `web-design-guidelines` 一条,**64 个技能一个没动**。**⑦ 字体统一**:他的要求是 `我想你用和右下角选择模型里的英文和中文一样的字体,这样才显得跟 DSH 比较统一`。**先量再改**(避免又猜):把模型选择器 `DeepSeek V4.1 Flash` 和面板里每处文字的 `fontFamily/size/weight/lineHeight` 全读出来对比,结果**字体族本来就一样**(都是继承 DSH 的 `--dsw-font-family`),真正不一致的是**技能名那一处**——它被我刻意写成了 **`--ds-font-family-code`(等宽代码字体)**,当初的理由是"`/技能名` 看起来像命令"。另外面板行还落在**浏览器给 `<button>` 的默认字号 13.3333px** 上(不是我声明的)。**修法**:新增 `FONT_FAMILY = var(--dsw-font-family, …)` 并**在面板根、搜索框、行、名称、描述、状态行、菜单**全部显式声明(不再靠继承碰运气),字号对齐 composer 的节奏(正文 14px / 行高 22-24px、次要文字 12px);`ui-monospace` 依赖清零。**实测**:模型选择器 `14px / 400 / line-height 24px`、面板技能名 `14px / 400 / 22px`、搜索框 `14px`、描述与页脚 `12px` ✅。**教训**:我第一版探针又量错了元素(抓到了外层容器而不是 `nameStyle` 那个 span),所以第一次得出结论"字体本来就一样"——**量要对准真正落到属性上的那个元素**。**⑧ 分组小标题 + 去掉 emoji**:他继续追问 `这两个也是吗?感觉看着不像啊。就是这个小标题,还有,我感觉这图标没必要吧。加图标,那前面一团火,这种图标看着不像 DSH 的风格`。**照旧先量再改**:拿 **DSH 自己的分组标题**(侧栏「工作区」)当基准,测出我那个小标题是 **11px / 600 / letter-spacing 0.44px**,而 DSH 是 **14px / 400 / normal** —— 又小又粗还带字距,**这就是"不像"的全部原因**;改完两者**逐项一致**(连颜色都是同一个 `rgb(129,133,140)`)。**图标全部去掉**:分组标题的 📌/🔥/🗂️、右键菜单的 📌/⏻/📂/🗑、底部入口的 ⏻ 全部改成纯文字(分组标题本来就是标题,DSH 的标题也是纯文字)。**唯一保留的是行尾那个置顶针**(📌/📍)—— 它是有功能的开关(点一下置顶),只有置顶的行才亮。**后来他也把这一条否掉了**:`那个置顶的按钮也不需要了吧,毕竟右键它就能有置顶` —— 于是**行内那个针也删了**,置顶/取消置顶只走右键菜单(「置顶」分组本身就说明了哪些被置顶)。至此**整个面板零 emoji**(实测 `anyEmojiInPanel: false`),行内只剩名称 + 描述。**⑪ 底部提示的最终形态**:他先说 `这句话感觉有点影响美观,因为它和上面的都对不齐,而且它又显示不完全` —— 于是**对齐 + 缩短**,并做成"用过一次右键就永久隐藏"(`localStorage` 标记)。他看完之后改主意:`我感觉你变成这一行就不错,那你就可以永久留着了,因为它一直显示很短,可以留着` —— 于是**撤掉一次性逻辑,永久显示**「右键可管理 · 手机长按」(已关闭视图里是「右键可开启 · 手机长按」)。**实测**:连开三次(含用过菜单之后、整页刷新之后)提示**都在**、`aligned: true`(左边与行文字对齐 = 列表 6 + 行 10 的 margin-left:16px)、`truncated: false`(不再出现「手机…」)✅;那一版的一次性标记已从代码删净(`showHint`/`RC_HINT_KEY` 0 命中)。**⑫ 搜索框**:他的要求 `我这个搜索框能不能变成这个模型搜索的样式?我感觉确实可以跟 DSH 去统一一下`。**这次没能量到基准** —— 模型选择器里那个搜索框**不是 `<input>`**(三次探针分别在 input 列表、占位符匹配、全元素扫描里都找不到它),所以按**他两张截图的视觉差异**改:**去掉那 1px 边框**(透明边框以保持几何不变),保留淡填充;因为无边框就没有焦点提示,补上**聚焦时一圈细环**(`boxShadow: 0 0 0 1px`,用 React 的 `onFocus`/`onBlur` 实现 —— 内联样式写不了 `:focus`)。**实测**:失焦 `border: 1px solid rgba(0,0,0,0)` + 背景 `rgba(38,49,72,0.06)` + 无环;聚焦背景升到 `0.1` + 一圈 `0 0 0 1px`;**尺寸始终 324×36**,不会跳动 ✅。**后来他又微调高度**:`改成30看看,就是行高减个六` → `再加回3吧` → `32吧`。**关键坑(值得单独记)**:**前两个数字根本没生效** —— 面板是 `flex-direction: column` + `max-height` 的容器,**输入框作为 flex 子元素被压缩到了 21px**,所以不管写 30 还是 33 渲染出来都是 21(他"看不出变化"正是这个原因,我还只改了数字)。直到给输入框加 **`flex: none`**,声明的 `height` 才第一次真正生效 —— **在 flex 容器里改子元素高度,先确认它没被 `flex-shrink` 吃掉**。最终**实测 `height: 32px`** ✅。**⑨ 关闭的技能还出现在主列表里(他报的,严重)**:`我不是关闭了吗?这列表里居然还有他`。**先排除最常见的嫌疑**:全盘搜 `web-design-guidelines` 的副本 —— **没有第二份**,磁盘上就那一个目录、且确实是 `SKILL.md.disabled`(他自己 8-13 关的)。**根因**:取列表有**两条路** —— 官方 skills API,和「本地扫描」兜底;为了「已关闭」视图能工作,**我的扫描器是故意会返回 disabled 条目的**,但前端把两条路的返回值当成了**同一种形状** ✗ → 关掉的技能就从兜底那条路混进了主列表。**修两层**:① 扫描结果里 `disabled === true` 的直接不进主列表;② 主列表**再按名字排除**所有已关闭的技能 —— 这是唯一的构建点,**不管数据来自哪条路都兜得住**。**实测(用他真实的那个技能验,不造数据、不写任何文件)**:主列表 `mentionCount: 0` ✅、在主列表里主动搜 `web-design` 仍是 0 ✅、底部入口显示 `已关闭 1` ✅、「已关闭」视图里**恰好出现 1 次** ✅;测试后复验磁盘:仍关闭的只有他这一条、技能数 64 不变。全量 `npm test`:**88 tests / 87 pass / 0 fail**
|
|
144
145
|
- **v0.5.25**:**修 ⚡ 面板的弹出位置:改成贴窗口右边缘,不再横跨正文**——国豪反馈:`我感觉点这个按钮,列表出现的位置不太好。如果再靠右显示就好了`。**根因**:面板原本是 `position: absolute; right: 0`,**贴的是 ⚡ 按钮自己的右边缘** —— 而 ⚡ 挨着模型选择器、离输入框右边缘还有 200 多像素,所以面板被"右对齐"到那个位置,于是往左横跨半个屏幕、**压住对话正文**。**修法**:改成 `position: fixed` + `right: 24px`(贴窗口右边缘),纵向偏移仍由 ⚡ 按钮的 `getBoundingClientRect()` 量出来(**打开前就量好**,避免第一帧跳一下)。**实测(真实浏览器里量的坐标,不是目测)**:面板右边缘 x=**1488**、窗口宽 **1512** → 距右边 **24px** ✅;改之前它右边缘只会落在 ⚡ 按钮那里(x≈1083)。全量 `npm test`:**88 tests / 87 pass / 0 fail**
|
|
145
146
|
- **v0.5.24**:**删掉面板底部那两个徽标(「本地扫描」/「/ 增强:运行时接管」)**——国豪的反馈:`确实有了,不过本地扫描和增强感觉没必要啊,感觉有点占地方,有点多余`。他说得对:**它们是"给排障的人看的",不是给用户看的**,却常驻占掉两行。**但没有直接丢掉** —— 「插件一切正常」和「插件压根没在跑」在界面上一模一样,这正是 **issue #14** 的教训,所以信息搬到了**不占地方的两处**:① ⚡ 按钮的 **tooltip**(鼠标悬停才显示,如 `选择技能(插入 /技能名 到发送框) · 列表来自本地扫描 · / 增强:运行时接管`);② **控制台一行**(面板每次打开打印一次:技能条数 + 列表来源 + `/` 增强是否真的生效)。顺带把已经没人用的 `sourceBadgeStyle` / `sourceBadgeTextStyle` 一并删除。全量 `npm test`:**88 tests / 87 pass / 0 fail**
|
|
146
147
|
- **v0.5.23**:**⚡ 面板里可以直接管理技能了:关闭 / 开启 · 定位 · 卸载(可撤回)**——起因是国豪视频评论区有人问"能加一个管理 skill 功能吗?实现删除、加、启动、关闭等操作"。查证结论:**官方没有任何技能管理界面**(把 asar 里 289 个官方包全扫了一遍,没有禁用/卸载技能相关代码;评论里说的"专门的插件"指的是 `find-skills` 那种**搜索+安装**的 skill,只解决"加"),所以这块是真空白。**交互方式(国豪的主意,比我的第一版好)**:动作**不占行内 UI**,而是**右键技能行弹出一个小菜单**(置顶 / 关闭 / 定位 / 卸载),手机则**长按 500ms** 弹同一个菜单(滑动即取消,长列表滚动不会误触);行内只留一个 glyph(📌/📍,兼作"已置顶"状态指示)。**踩到的 UI 坑**:关闭监听最初挂在**捕获阶段**,会在菜单项自己的 `onClick` 之前就把菜单关掉 → 点了没反应;改成**冒泡阶段 + 菜单自身 `stopPropagation`**。**①「关闭」是真关闭,不是障眼法**:读了官方 `dsh-skill-filesystem` 的判定逻辑 —— **一个技能 = 目录下有 `SKILL.md`(大小写严格)**,或技能根下的顶层 `.md`。所以关闭就是把 `SKILL.md` 改名成 **`SKILL.md.disabled`**:provider 直接看不见它,**agent 也不再加载**,要回来只需改回名字。**②「卸载」从不删除**:移进 `$DSH_HOME/skill-backups/<时间戳>-<名字>/`(旁边写 `manifest.json` 记原路径),面板里出现「撤回」;`skill-ops.js` 里**一个破坏性文件调用都没有**,而且有一条**结构性回归测试**盯着这件事(注释里出现过破坏性调用的名字都会让它红 —— 这是它第一次触发时教我的)。跨盘无法 `rename` 时**直接报错**,绝不退化成"复制+删除"。**③ 三道安全闸**(这些路由**不需要鉴权**就能到达,所以不是可选项):请求必须带 `x-dsh-skill-picker: 1` 自定义头(跨站表单 POST 设不了自定义头,CORS 预检 host 也不应答);**客户端只报技能名,路径由 host 自己查**(⚡ 面板主数据来自官方 API,那套 DTO 不保证带 `path`;而且浏览器说了不算);路径必须**恰好落在已知技能根的一层之下**(`../` 逃逸、根目录本身、技能内的文件、任意绝对路径全部拒绝)。**④ 一个必须补的洞**:官方 `skills/list` **不会返回已关闭的技能** → 关掉之后就再也找不到、开不回来了;所以面板额外拉一次 host 扫描,单独渲染「⏻ 已关闭」分组专门用来开回来。**⑤ 真机端到端实测(用我自己建的假技能,全程只动它一个)**:关闭后磁盘上 `SKILL.md`→`SKILL.md.disabled` ✓、**扫描仍能列出它且 `disabled:true`** ✓(这是"能开回来"的前提)、开启还原 ✓、卸载后原位置消失而备份里 `SKILL.md`+`manifest` 完整 ✓、`backups` 能列出 ✓、`restore` 回原位 ✓、不带自定义头 → **403** ✓、`name` 塞 `../../../Windows/System32` → **404** ✓、`GET` → **405** ✓;**测试后你的技能数一个没变、备份目录零残留**。**回归测试**:新增 `test/skill-ops.test.mjs` 13 例(路径闸 9 种情形、关闭/开启往返、重复关闭拒绝、开启未关闭的拒绝、卸载后原位置消失但备份完整且 manifest 正确、撤回后再占用时拒绝覆盖、备份原路径被改成根外后不可撤回、备份 id 不允许带路径、**"模块里不存在破坏性调用"**、无 manifest 的杂目录被忽略、备份目录必须落在 `DSH_HOME` 下)。全量 `npm test`:**88 tests / 87 pass / 0 fail**
|