Skip to content

Releases: Imzl-zl/dsh-toolbox-ui

dsh-toolbox-ui v1.8.3

Choose a tag to compare

@github-actions github-actions released this 23 Oct 07:16

dsh-toolbox-ui v1.8.3

这一版修的是一件事的放大效应:有项目 MCP 的工作区里,新建一个会话会卡住十几秒(issue #8)。 顺带修掉三处「工具看着在、点了不对」的正确性缺陷。

修了什么

1. issue #8:有项目 MCP 时新建会话卡顿

项目 MCP 的工具必须复制进每个会话自己的工具层 —— 这是平台结构决定的,不是实现选择:

  • 平台没有「工作区级工具来源」,作用域只有 global / preset / agent 三档;
  • agent 唯一的父级槽位在桌面版被 preset 占着(bindScopeParent 对已绑定的 key 直接抛错),所以拿不到「继承」这条路;
  • 公开读面也没有便宜的读法:get(name, scope) 每次重建整张表,schemas(scope) 对每个工具的 parameters 做全量深拷贝。

原实现对每一次 tools/change 都把「读全表 + 对账 + 注册」重做一遍,而一次 207 个工具的工具表变化会派发 200+ 个事件、且每个存活会话各算一遍。代价因此是 O(事件 × 会话 × 工具)。

现在按连接的代次缓存:mcp-client 每次工具同步都整批重建定义对象,所以「缓存里那个哨兵定义还在原处」⇔「这条连接的工具集没变」⇒ 一次读取都不付;真变了才读,且只读一次。事件侧再把同一个微任务里的变化合并成一轮。

真宿主 + 真 stdio、207 个工具、交替两轮实测(同一台机器同一时段,绝对值受负载影响,看比值):

指标 v1.8.2 v1.8.3
首会话里 register() 的同步段 998 / 1359 ms 29 / 83 ms
8 个存活会话后一次 tools/change 72–104 / 68–83 ms 0.68–2.19 ms
第 8 个会话创建 329 / 210 ms 20 / 45 ms
重复注册告警(旧实现每个会话挂载时报一条) 3 条 0 条

2. 重连 / 改完配置之后,会话里的工具是「死的」

旧实现只按工具名字对账。连接换代后名字不变、定义对象全换了,于是会话里留下的是指向已经关掉的那条连接的旧定义 —— 调用失败,而且看不出哪里不对。现在按定义对象本身对账,连接换了就换成新的那一版。

3. 读不到工具视图时,会把整批工具卸掉

作用域视图读取失败时,旧实现把它当成「这台服务器现在一个工具都没有」,于是把会话里已装的工具全部 dispose(),等下一次变化再装回来 —— 用户看到的是工具时灵时不灵。现在读不到就保持原状(不增不删),只在面板上记下原因。

4. 内部:删掉了一层不必要的「投射期间忽略事件」

旧实现假设「同步代码块里派发的事件必然是本次投射自己发的」,于是把投射期间的事件全部忽略。这条不成立:emit 是同步派发,同一次派发里别的监听器也会同步注册工具(DSH 自己的 tool-subagent 就是这么干的),那些变更会被一起吞掉。删掉后改用「合并 + 幂等重算」,实测各指标持平或更好。

验证

  • 本地 node --test:833 / 833。
  • 真宿主(真 cordis + 真 dsh-tools + 真 dsh-mcp-client + 真 stdio 子进程),207 个工具:8 个会话各自投射 207 个工具,经真 stdio 执行成功,重复注册告警 0 条。
  • 真模型闭环:模型看到的就是会话里投射出来的 schema,返回 tool_call,实参打到真 stdio 子进程并回灌,最终回复一致。
  • 新增三条回归用例(旧实现上为红):稳态只付一次哨兵 get();连接重建定义后必须刷新成新定义;读失败不得卸载已投射的工具。
  • 新增两个探针:.tasks/issue-8-tool-view/probes/sentinel-contract-probe.mjs(验证上游「每次同步整批重建定义」这条前提,上游一改就红)、panel-idle-probe.mjs(把面板轮询成本从事件队列的尾巴里分离出来)。

升级

dsh plugin --profile web update dsh-toolbox-ui

新装同样一条:

dsh plugin --profile web add dsh-toolbox-ui@^1.8.3

升级后重启 dsh web(桌面版重启应用)。

仍然存在的边界(说清楚,免得下次当成 bug 报)

  • 换代时仍要付一次 schemas(scope) 全量深拷贝(207 个工具约 20 ms)。公开读面拿工具名字只有这一条路;它只在 tools/list 变化 / 重连 / 就地重载时发生,稳态不付。
  • 面板每 5 秒仍要读一次 schema(详情要显示参数),成本与工具数成正比,不在事件链上。
  • 每条项目连接的工具仍要复制进每个会话的层 —— 平台没有工作区级工具来源,这一点插件改不了。

dsh-toolbox-ui v1.8.2

Choose a tag to compare

@github-actions github-actions released this 20 Oct 12:05

dsh-toolbox-ui v1.8.2

一版全是修复,而且都是「看着能点、点了不对」那一类。 两条主修复:市场页的「移除」从来没成功过;同名条目会把整行锁死,导致你既改不了也删不掉。

修了什么

1. 市场页「移除」从来没成功过

点「移除」时,客户端把参数包成了对象({ name })发给宿主,而 mcpManager/removeServer 的契约是一个标量 name。客户端代理按位置把实参映射到描述符的 wire 字段,于是宿主收到的是 { name: { name } },被 String codec 在边界挡下:

typert gateway: mcpManager/removeServer: wire field "name" failed boundary validation

而面板把这条错误画成了「读取目录失败」——看起来像目录坏了,而不是移除失败。现在真的会删;失败时标题是「移除失败」,来源与文案对得上。

同批修掉的还有一处静默缺陷:技能市场的分类接口少声明了一个参数,locale 在发出去之前就被按实参个数拦下,错误又被调用处的 try/catch 吞掉,所以英文界面里分类名一直是中文、且没有任何报错线索。

2. 同名条目不再把整行锁死

一个 serverName 在 loader 里可能对应多条条目:别的 bundle / agent preset 提供的,或更早的配置把同一行写重复了。旧逻辑要求「全场只有一条同名条目」才认自己那条,于是别人多一条 = 你这台编辑 / 停用 / 重连 / 移除全部变灰,只留一句「冲突,只读」。

现在区分两个判据:

  • 能不能改:只看 loader 条目的 id 是否等于 profile patch 里那一行的 id。写操作只落到这一条,别的层一个字节都不动;
  • 有几条同名:只作信息——详情里点名另一条的 id 与状态,并说明 mcp-client 在连接期按作用域判重、同名里只有一条连得上。

移除之后如果还剩下别层的同名条目,提示会说明「另一条没动、要重启宿主才会重新连接」,不再用一个泛化文案盖掉。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web(桌面版重启应用)。更新提示每天最多查一次、进程内缓存 24 小时,重启宿主后立刻生效。

顺带:仓库的 latest 回到本线

旧包 dsh-mcp-manager-ui 冻结在 1.5.5,而 DSH 0.2.1 对它是硬不兼容(老包 peer 是 >=0.1.5-rc.1 <0.2.0,本线是 >=0.2.0-rc.1 <0.3.0,两个窗口互斥),所以本线的 Release 从这一版起不再标 pre-release,releases/latest 指向 dsh-toolbox-ui。旧线用户升级路径见 README 的迁移说明。

验证

  • 本地 node --test:829 / 829。
  • 新增 test/client-wire-shape.test.mjs:拿出货的 typert 描述符与面板调用点对撞,检查实参个数与标量/对象形状——把本次修复还原回去它立刻变红。
  • 冲突语义:test/host-import.test.mjs、test/tool-search-wiring.test.mjs、test/detail-panes.test.mjs 三处改为「只动自己那条」,并新增「另一条被点名、本行仍可编辑、别层条目仍只读」的用例。

dsh-toolbox-ui v1.8.1

dsh-toolbox-ui v1.8.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Oct 08:59

dsh-toolbox-ui v1.8.1

延迟开关不用再进「编辑」框了。 1.8.0 里列表行上那颗「延迟」只是个状态标签:想给某一台开延迟,得点进编辑弹框勾一个复选框,而列表上看不出那个动作在哪。这一版把它做成可以直接点的开关。

现在

每个服务器行上都有一颗可点的标签,两种状态都显示:

  • 显示「延迟」(闪电、accent 底)= 这台的工具有延迟加载,模型经 tool_search 按需搜索;
  • 显示「全量」= 这台的工具每轮都常驻上下文。

点一下就切,写入配置并当场生效,不用开弹框、不用重启。悬停有提示,键盘 Tab 也点得动(读屏会念出"已按下/未按下")。集群外的条目(项目视图里看到的全局条目、别的 bundle 的条目)那颗标签只展示不可点——它们得回到自己的作用域里改。

想一次全开/全关,仍然用列表顶部的 ⚡「全部延迟」;编辑弹框里那个复选框也还在(批量编辑和规则说明还在那儿)。

为什么做成"芯片"而不是开关

右边的开关是「启用/禁用」——关掉就整台不加载、工具全没。延迟只是"工具放不放常驻上下文"。两者长得一样会让人以为关掉延迟等于关掉服务器,所以延迟用的是可点的芯片(和「技能市场」分类筛选同一个视觉语言),状态靠 accent 底色 + 文案 + aria-pressed 表达。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web(桌面版重启应用)。

验证

  • 0.2.0-rc.2 基线 node --test:827 / 827;0.2.1-rc.2(cordis 4.0.5 + loader 1.0.6)矩阵:779 / 779。
  • 真 loader 用例:单台只改被点的那一台(旁边那台一位不动)、磁盘上用生产解析器读回、内存活条目同步(否则运行中的会话读旧值)、名字对不上时明确报错。
  • 真浏览器点穿:点「全量」→ 变「延迟」+ 一条宿主原话的提示 + profile patch 里出现 defer: true;反向同理;无障碍树里能读到 aria-pressed 状态。

dsh-toolbox-ui v1.8.0

dsh-toolbox-ui v1.8.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Oct 07:55

dsh-toolbox-ui v1.8.0

工具不再非全量常驻上下文了。 这一版是 ToolSearch 延迟加载:把某个 MCP 服务器的工具定义从「每轮都塞进模型上下文」改成「模型按需搜索、命中后才进工具列表」,复刻 Claude Code 的同名机制,插件层实现。顺带支持 DSH 0.2.1 宿主。

为什么值得升

一个接满 MCP 的会话,上下文里挂着几百个工具定义,每个请求都在付这笔钱;而一次给模型看超过 30–50 个工具,选择准确率还会明显下降(Anthropic 公开数据;DSH 官方文档「工具搜索」一节同源)。延迟加载让没被用到的工具定义根本不进提示词。

怎么用

默认不延迟,逐台打开:

  1. 面板 → MCP → 服务器行的「延迟」开关;或列表顶部一键「全部延迟」(全局/项目两个作用域各写各的文件)。
  2. 也可以直接写配置:全局在 profile 的 cordis.patch.yml 里给那条 mcp 条目加 defer: true,项目在 .dsh/mcp.json 里同理。
  3. 想整体关掉、退回全量注入:环境变量 DSH_MCP_TOOL_SEARCH=off。

打开后模型看到的是只列工具名的目录 + 一句引导(没有描述、没有参数 schema)。它用内置的 tool_search 搜(关键词,或 select:<确切工具名>),命中的工具的完整 schema 才进入它的工具列表,之后可直接调用;凭记忆直调未加载的工具会被守卫拒绝,并在拒绝里给出确切的那条查询串,照抄即可。

设计过程中产生的三个键不存在:descriptionChars、threshold、minServerTools——写进配置会显式报错(不是静默忽略)。真正可调的是下面这组。

可调的三个上限(面板 → MCP → 顶部「延迟参数」)

配置键(config.toolSearch 下) 默认 它决定什么 凭什么
maxResults 10 一次 tool_search 最多返回几个工具 实测一台 28 工具的服务器:只返回 5 条漏掉 33% 的核心工具,10 条全部找到;32 工具那台两种限额本就都 100%
maxCatalogLines 300 目录最多列多少个名字 实测一台 360 工具的真实服务器:每行 32.66 字符、全量 360 行 12,514 字符;上限 300 时会漏掉 60 个名字,所以它是个成本上限而不是"最优点"——想要更多名字就自己抬(同一批工具的完整定义是 159,136 字符,目录只占 7.9%)
maxDiscovered 20 已发现的工具上限(它们会和常驻工具一起进工具列表) 常驻工具通常 10–15 个,20 + 12 ≈ 32,落在官方 30–50 退化带的下沿

保存后写进 profile patch 的 config.toolSearch,并在当前进程里立即生效——不需要重启,也不需要重开会话。实现上是「写盘 + 重装运行时的 ToolSearch 装配」:maxResults 的取值范围与默认值写在模型可见的工具定义里,只改内存会让模型看到旧上限(那是假生效)。与默认相同的值不写盘,所以将来默认值有更好的实测依据时会跟着走,不会被这次抄下来的数字钉住。

手改配置文件也可以,但 patch 不会被热加载,所以手改要重启 dsh web。

这个功能为什么长成这样

下面是设计与实现过程中被实测推翻的几件事。它们不改变「你以前怎么用」(这是新功能,之前没有),但决定了你现在看到的样子。

① 目录只列工具名,不带「一行用途」。 最初的设计是每行附一段用途、在 110 字符处截断。实测 27 个真实工具(filesystem 14 + everything 13):56% 的描述会被切,而被切掉的尾巴里 80% 带约束/条件词(only / not / never / when…)——read_file / read_text_file / read_media_file 这类近义四兄弟,区分它们的信息恰恰在尾巴上。所以按 Claude Code 的形状改成只列名字:同一份样本里目录体积从 3401 字符降到 954 字符(28%)。

② 没有自动豁免门槛。 实现中曾有两道「工具数太少就不延迟」的门槛(per-server ≤10、会话里可延迟工具 <15),两道都过才真正生效。官方没有这两道(唯一的阈值模式是按体积百分比,不是按个数),而它们的实际效果是「装 1 台 30 工具的会启用、装 1 台 12 工具的啥也没有,面板却显示已开启」。现在勾了就生效,判断权交给用户;工具少时面板会直说「省下的空间可能不多」。

③ PTC 会话原本净亏,现在真省。 agent 被 preset 设成 ptc 时,遮罩对工具的常驻投影是 no-op,而宿主的 tools:sdk 手册仍列全部 schema——装插件反而倒贴 3181 字符/请求。现在装配时裁 tools:sdk(把子集喂回官方渲染器重渲染,逐字节相等;自检不过就沿用原文):同一场景 renderPrompt 从 12184 → 5315 字符(真省 3688)。装配判据也改成 per-agent,native 与 ptc 都启用。

④ 会话内恢复。 发现集记在 tool_search 结果的消息元数据里(渲染时剥掉,不泄露进提示词),重建/resume 的会话从会话日志还原已加载的工具;PTC 会话另走一条内部投影。全部发现之后目录与引导段会真正消失(D = managed − discovered,三处投影同一条式子)。

⑤ DSH 0.2.1 宿主。 两条线之间宿主换了两代 loader(1.0.3 → 1.0.6):启动错误传播变严格(以 fiber.await() 为准)、dispose 写盘行为不同。插件对两处做了显式适配,peer 窗口相应改为 >=0.2.0-rc.1 <0.3.0。只支持 0.2.x:per-agent 呈现模式、tools:sdk 手册段、会话投影都是 0.2 才有的接口。

验证

  • 0.2.0-rc.2 基线 node --test:825 / 825;0.2.1-rc.2(宿主自带 cordis 4.0.5 + loader 1.0.6)矩阵:777 / 777。
  • 真机点穿(隔离 DSH_HOME + 发布的真宿主 + 真模型):提示词只说「用某个只出现在工具描述里的词的能力」(工具名本身不含该词),模型先 tool_search、命中后真调到该工具,回报的随机数与夹具服务器调用日志完全一致(692515 = 692515);另一轮让它逐字引用目录,返回的确实只有 - mcp__<server>__<tool>(names-only 在真 prompt 里成立,不只是单测里成立)。
  • 参数开放也在真机上点过:profile patch 里写 config.toolSearch.maxResults: 3,真宿主里模型引用到的是 "Maximum tools to return (1-3); defaults to 3.";面板在真浏览器里保存后,patch 出现 - id: mcp-manager-ui / config.toolSearch(别人的条目与注释原样),重开读回新值,「恢复默认」把那条覆盖整条删掉。
  • 真机验证抓到并修掉了两个缺陷(都是夹具全绿也存在的):① 插件按 bundle 安装时,用户 patch 里没有我们那条条目,旧实现保存必然失败——现在改为写宿主的 id 定向覆盖;② 面板的 call() 失败时返回错误对象而不是抛异常,旧实现没查,于是写盘失败被当成成功(界面弹了「已保存」而文件没动),读失败时还会让面板整片崩掉。
  • 真 Loader + 真 mcp-client + 真 stdio 子进程覆盖「面板加/删 = 运行中进程真的创建/停掉条目」,包含重启后 patch 行不重复、cordis.yml 一个字节没动的反向对照。
  • 真机 UI 跑过「装得上 + 面板出得来」(真宿主加载、面板渲染出真实服务器与工具数)。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web(桌面版重启应用)。

dsh-toolbox-ui v1.7.2

dsh-toolbox-ui v1.7.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Oct 01:31

dsh-toolbox-ui v1.7.2

技能市场的分类,从上线那天起就没拉到过。 这一版修两件事:分类接口的响应形状解析错了(分类整个不显示,市场只剩「全部」),以及分类名的英文一份一直在上游手里、我们没拿。

你看到的以前的样子

  • 打开「技能 → 市场」:没有分类那一排——只有一个「全部」按钮,13 个分类一个都不显示;
  • 英文界面下即使有分类,也会是中文名。

根因(这次是拿真实响应对的,不是猜的)

① 响应形状从没对上。 上游 /api/v1/categories 真实返回 {count, items: [...]},我们的代码读的是 body.data——永远读不到,hubCategories() 返回空表,分类行整个不显示。测试夹具照抄了代码里错的形状({data: [...]}),所以 614 条用例全绿也没人发现。夹具照抄实现,就证明不了实现。

② 上游给了中英两份,我们只读一份。 真实响应里每个分类都带 name(中文)和 nameEn(英文):

{"key":"office-efficiency","name":"办公效率","nameEn":"Office Efficiency"}

代码只读 name——和 1.7.0 修的 description/description_zh 是同一类错。

现在

  • 按真实形状 {count, items} 解析;形状不符时抛错,不再静默返回空表(一个没有分类的市场比一句报错难排查得多);
  • 分类名按界面语言挑(nameEn/name,缺一侧回落另一侧),默认中文行为不变;
  • 切语言时市场按新语言重拉(分类与技能描述一起)——此前 effect 依赖里没有语言,切了也停在旧语言;
  • 子分类名上游只给中文(实测 90/90 无 nameEn),按上游给的显示,不自己编。

验证

  • 单测 614 项全部通过;分类用例的夹具换成真实响应形状(count/items + name/nameEn),含默认/显式英文/带地区 en-US/缺 nameEn 回落/形状不符抛错;
  • 两条闸门实测会红:只读 name 不读 nameEn → 红;分类调用不传 locale → 红;
  • 分类响应是打了真实上游核对过的(本机直连 api.skillhub.cn),不是照文档猜的。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web(桌面版重启应用)。

dsh-toolbox-ui v1.7.1

dsh-toolbox-ui v1.7.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Oct 01:14

dsh-toolbox-ui v1.7.1

修两个语言 bug,都是真机报的。 1.7.0 的英文变体在 web 上基本对了,但有两个角落是坏的:中文宿主持面板整片英文;桌面版切语言后面包板不跟随。这一版把两个都修了。

Bug 1:DSH 是中文,面板却显示英文(web)

你看到的以前的样子:DSH 界面是中文,面板里按钮、页签、状态全是英文——只有 MCP 市场的分类筛选标签是中文。

根因(读宿主源码定位,不是猜):宿主按「当前语言 → 回落链」查每个插件登记的词典,LocaleRuntime.fallbackChain 把英文设为每条链的终点(zh 的 fallback 是 en,链末尾还强制追加 en)。我们只登记了英文一份词典——中文模式下链是 ['zh','en'],zh 未命中,落到 en 命中,整片面板变英文。分类标签不走词典(走数据字段),所以它独苗中文。宿主自己的 register 契约也写明双语命名空间「every shipped locale is required」——单语言登记就是违规用法。

现在:中英两份都登记(中文那份是恒等词典:键就是中文原文)。中文模式恒中文,英文模式不受影响。

Bug 2:桌面版切换语言,面板不跟随

你看到的以前的样子:桌面版打开是中文,去设置里把 DSH 切成英文,面板一动不动(web 版一直是正常的)。

根因(同样读源码):桌面壳给宿主的语言服务挂了一座原生桥(__DSH_LOCALE__.read() 是异步的),语言插件的启动要等它才 provide 服务;而面板只在启动时接一次线——桌面版这次 ctx.get('locale') 当场是 null,从此再没接上。web 没有这座桥、同步 provide,所以 web 一直是好的。

现在:启动时探一次,没探到就把 ctx 留下;侧栏入口和面板挂载时补接(幂等)。桌面版切语言即时生效。不改用 inject 声明依赖——宿主没有语言插件时那会让整个面板不出现。

验证

  • 单测 614 项全部通过(新增 2 条,都是这两个 bug 的精确闸门);
  • 两条闸门都实测会红:把登记改回单语言 → 中文模式闸门红;去掉迟到补接 → 桌面场景闸门红;
  • 假 locale 服务重写成忠实模拟宿主回落链的版本——旧假件在非英文时原样返回键,模拟不出「落到英文」这条链,所以 Bug 1 当时可以全绿通过;
  • CI 在 main 上通过。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web(桌面版重启应用)。从任何更早版本升级无迁移动作。

已知限制:技能市场的分类名仍是上游中文(上游只提供中文名),本地映射表在下版做;技能描述文案已按语言切换,不受影响。

dsh-toolbox-ui v1.7.0

dsh-toolbox-ui v1.7.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 Oct 18:01

dsh-toolbox-ui v1.7.0

面板会说英文了。 这一版的主题是「英文变体」:面板文案、五份文档、两大市场的文案都有了英文版——而中文仍是默认,一个字节都没动:没有语言服务的宿主上面板照常纯中文,中文用户看到的与升级前完全一致。

你看到的以前的样子

  • DSH 界面切成 English,面板里仍是中文——按钮、页签、状态、提示,没有一个字跟着变;
  • 英文读者点开 README 的「Documentation」,只有中文文档;
  • 英文界面下,MCP 市场的分类行、预设模板下拉、凭据要求是中文;技能市场明明是上游给了中英两份文案,界面却一律显示中文。

现在

  • 面板跟随 DSH 语言:宿主切英文,全部文案即时变英(不重开面板);中文默认不变;
  • 五份文档英文版:installation / skills / json-import / project-mcp-lifecycle / design,README 中英互链,代码块与字段名逐字节未动;
  • MCP 市场英文化:分类、说明、凭据要求、占位符提示;按中文名或英文名都搜得到;
  • 技能市场按语言挑文案:上游本来就给两份(发布方原文 + 平台中文版),以前英文那半永远用不上;
  • 「这次变了什么」提示双语:从 1.3.0 起的历史条目全部补齐。

三个值得说的设计

① 中文字面量当词典键,不设符号键。 EN_DICT 的键就是界面上的中文原文。好处:650 处调用点不用为取名再想一遍,键与文案永远不可能对不上;漏翻时界面显示中文原文(可读),符号键漏了显示 mcp.some.key(不可读)。

② 英文不是兜底,是被显式选中的语言。 初版写成「locale 服务不在时也出英文」,真机上没有 locale 的宿主整片面板变英文。改为只在宿主 active 语言明确是 en* 时出英文——没有语言服务 = 没有语言切换能力 = 原文。这条有测试钉着。

③ 中文核实记录与英文译文是两份文件。 .tasks/mcp-servers-verified.yaml(逐条一手核实的基准)一个字没动;英文是可选的翻译副本,生成器把它读成 *En 字段。译文可以独立重译,配置数据(url/command/args)只有中文一份真相源——生成器对两份文件做逐条 parity 断言,译文改配置直接报错。

这一版还修了(独立审查发现,均已按根因修)

  • 预设模板下拉、市场分类行在英文界面漏中文(求值时机/字段漏搬运);
  • gitlab / netlify 两条市场文案的英文版丢掉了中文里的 ⚠️ 警告(「不要美化」红线,现有 parity 闸门);
  • 英文 YAML 副本此前的 # 守卫只护中文不护英文,且对「YAML 注释截断」这类问题存在盲区——改为原始文本层 + 解析值层双层检查,中英文都查;
  • 英文文档 10 处目录锚点死链(中文锚点对着英文标题);
  • 侧栏「工具箱」入口标签以前停在插件加载时的语言——改用宿主 SlotLabel 的 thunk 契约,随语言切换即时更新。

验证

  • 单测 611 项全部通过(合并时 577 + 本版新增 34 条闸门:预设配对、模块级求值、slot thunk、标点/判据、生成器守卫 10 条、文档 parity 20 条、双语提示 2 条);
  • 每条新闸门都实测会红(注入对应缺陷 → 红 → 还原 → 绿);
  • CI(ubuntu + node 24,含真机集成用例)在 PR 与 main 上均通过。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web。从任何更早版本升级无迁移动作;英文变体不改变任何中文行为。

dsh-toolbox-ui v1.6.7

dsh-toolbox-ui v1.6.7 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 Oct 02:59

dsh-toolbox-ui v1.6.7

技能市场能翻页了。 这一版修的是一件事:市场以前只取第一页,而上游有 19 万个技能——滚到底没有任何反应,计数却写着「19 万条结果」,数字和屏幕上的卡片永远对不上。

你看到的以前的样子

  • 打开「技能 → 发现市场」,滚到底——什么都不会发生;
  • 计数写着「192936 条结果」,屏幕上只有 48 张卡片,且没有任何办法看到其余条目;
  • 换关键字搜索,新结果会和旧页面叠在一起,有时候像「搜索没生效」。

现在

  • 滚到底自动加载下一页,底部同时有「加载更多」按钮(键盘也能用);
  • 计数改成「已显示 X / 共 Y 条」——屏幕上有的和数字说的永远是同一批;
  • 换关键字 / 分类会重置回第一页并整体替换,不再叠加;
  • 上游偶尔抖一下时,已翻到的卡片原地留着,错误与重试显示在结果区上方。

两个值得说的根因

① 上游同一页就会给重复条目。 同一个技能被不同发布者多次发布——实测第一页(每页 100 条)只有 88 个不同技能,agent-browser、anti-fraud 各出现 3 次。重复条目带进界面后渲染会错乱(实测 297 张卡片 vs 计数 285)。现在按技能标识全局去重:跨页重复与页内重复一起去掉。

②「滚到底加载」不能依赖页面内部的重渲染时机。 第一版实现用的是 IntersectionObserver(进入视口自动加载),在隔离宿主里实测发现:面板被宿主外壳持续高频重渲染时,自动加载要等一次搜索交互后才生效——「第一次滚动不加载,交互过才加载」。现在改成挂在滚动区自己的滚动事件上,首次滚动即生效。

验证

  • 单测 568 项全部通过(新增 5 项,覆盖分页请求、追加去重、关键字重置、到头停止、失败不清空);
  • 隔离宿主真机复核(独立数据目录 + 隔离浏览器,打真实上游):全新页面不做任何交互、第一次滚动 → 88→184→273 张;关键字 excel → 92 / 共 2900 条;滚到头后不再发任何请求;控制台零错误。

升级

dsh plugin --profile web update dsh-toolbox-ui

升级后重启 dsh web。从 1.6.6 及更早版本升级无迁移动作。

dsh-toolbox-ui v1.6.6

dsh-toolbox-ui v1.6.6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 12 Oct 06:21

dsh-toolbox-ui v1.6.6

技能面的一次架构修复:这一版把「技能在磁盘上的位置」和「技能叫什么名字」彻底分开,并且换掉了启停的实现方式——从「改你的文件名」改成「写宿主原生的两个字段」。

这一版没有新功能。改的全是既有行为里站不住的地方。

为什么值得单独发一版

技能面此前有三个缺陷,看起来是三件事,其实是同一个根因:面板把技能身份(文件里的 name)当成了磁盘位置用。而 DSH 从不保证这两者一致。前两个版本已经为它付过两次代价(「目录是链接」→「单文件是链接」→ 现在「名字不一致」),所以这一版不再打补丁,改的是判据本身。

修了什么

文件名与技能名不一致的技能,在面板里一个都用不了

DSH 的技能名取自文件里的 name,磁盘上的文件/目录名允许与它不同(dir-name/SKILL.md 里写 name: dir-alias,宿主照收)。面板以前按技能名去拼磁盘路径(<根>/<技能名>/SKILL.md),于是这类技能:

  • 模型用得好好的(宿主侧一切正常),
  • 面板里点什么都报「找不到技能」——读正文、启停、删除全部失败。

实测六种形态里有四种完全不可操作。现在一律按宿主报告的真实位置定位:根取宿主给的展示路径,磁盘条目由「根下按真实路径匹配」查出,技能名不参与定位(只用于显示)。

「停用」不再改你的文件名

以前停用是把 SKILL.md 改名成 SKILL.md.disabled,借此让宿主扫不到它。这有三个问题:

  1. 它会在别的工具里弄坏你的技能。SKILL.md.disabled 不是任何规范认可的形态,而 ~/.agents/skills 是 Agent Skills 标准的共享位置——VS Code / Copilot、Codex 都读它。
  2. 状态只存在于文件名里,所以面板必须自己扫盘才能看见被停用的技能——这是第二份真值来源,也正是「同一行重复渲染」「链接技能停用后从面板消失」这些缺陷的出处。
  3. 恢复时要能算出正确的改名目标,而目标不能按技能名推(第一个问题的另一半)。

现在停用写的是宿主原生的两个调用面字段:

disable-model-invocation: true   # 模型不会自动调用它
user-invocable: false            # 你也不能用 /名称 手动调用它

这正是 VS Code / Copilot 官方文档里叫 “Disabled skills” 的那一档组合,而 DSH 原生支持这两个字段。于是:

  • 技能留在磁盘原处,别的工具照常认得;
  • 宿主照样报告它(它的目录是调用无关的),面板不必自己扫盘——那类缺陷在结构上不再可能;
  • 「已停用」是从这两个字段派生的显示,不是第三个要存的状态。

⚠️ 关于这个代价:详情页把两个调用面各自做成了独立开关,「停用 / 启用」只是「两个面一起拨」的快捷动作。所以万一你的「仅手动可调用」被一次往返重置了,在面板上把「模型自动调用」关掉就能拨回来,不必去编辑技能文件。四种组合(都能用 / 仅手动 / 仅模型 / 停用)都能在面板里直接拨。

顺带修掉的:诊断行被硬裁成半截字

真机复核时发现「需要注意」那一组的行里塞了整条根路径,而侧栏给这一格只有约 185px:实测内容宽 237px,且这一格没有省略号,于是那行显示成「已」后面什么都没有。路径本来就在组标题里,行内重复既冗余又撑破布局——现在只显示状态(「旧机制停用」/「已停用」),并给这一格补上了 min-width:0 + 溢出省略。

旧版本留下的 .disabled 仍能恢复

1.6.5 及更早的面板在你自己磁盘上写下的 SKILL.md.disabled 文件仍会被列出来(标为「旧机制停用」)并支持一键恢复。恢复后请用新的停用方式。这段兼容读取会在下一个大版本移除。

改完不用重启了(以前面板让你重启,其实是白重启)

详情页的开关提示一直写着「重启 DSH 最稳妥」。这句话不准确:宿主用文件监听盯住技能根,
改动通常不到一秒就影响模型(实测 100~300ms,受监听器的 200ms 稳定阈值支配),真机默认就是开着的
(presets 与 profile 都没有关掉它)。现在提示与文档都如实说明,并有一条用例钉住这个前提——
一旦宿主不再自动发现,那条用例会红,提醒我们改文案而不是让用户白重启。

顺带说明面板侧为什么做不到实时推送:宿主有 skills/change 事件,但它不在宿主转发的白名单里,
而那份白名单由宿主独占(只允许注册一个源,第二个注册直接抛错),第三方插件没有合法途径把它送到浏览器。
所以面板是「动作后重取 + 切回标签页时重取」,而不是假装能实时推送。

升级

dsh plugin --profile web update dsh-toolbox-ui
# 装完重启 dsh web

⚠️ 若你还在 1.6.1 及更早的版本,面板不会提示这一版(那些版本的更新检查读的是一条已经失效的数据源)。请手动执行上面这条命令升一次;从 1.6.2 起提示已恢复正常。

仍装着旧包(≤1.5.3)的话走迁移:面板点「升级」升到 1.5.5,再点「迁移」即可(它会装本包、卸掉旧包),详见 1.5.4 的说明。

兼容性:只改技能面,MCP 面与 1.6.5 完全一致。宿主要求不变(0.2.0-rc.x)。

验证

node --test → 562 通过 / 0 失败。

发布前做了前后对照:对 npm 上的 1.6.5 原始产物与本次工作树跑同一份验收脚本(node .tasks/_accept.mjs <版本|worktree>,解 tarball、按生产布局摆好 peer)。覆盖「用户完整形状」(DSH_HOME 落在工作区内 × 技能目录全是链接 × 两个工作区 × 磁盘名与技能名不一致)外加本轮新发现的三种形态(目录名以 .md 结尾、根级单文件、旧机制残留):

npm 1.6.5 本次
通过项 9/32 33/33

1.6.5 失败的正是本版修的东西:名字不一致、odd.md/ 目录、残留那三条的读正文/停用/启用报「找不到技能」;停用会把 SKILL.md 改名成 .disabled;停用后列表里的条目从目录消失;旧机制残留根本列不出来(所以用户永远救不回它)。

这一轮的重点是让测试网够得着真实形态,以及让独立审查先跑一遍:

  • 上述形状用真实宿主包(@deepseek-ai/dsh-skill + @deepseek-ai/dsh-skill-filesystem,0.2.0-rc.2)跑,断言落在操作真的改了盘上——「停用后那两个字段真的变成不可调用」「启用后逐字节还原原文」「删除只删链接、外部同步库一个字节没动」「恢复旧残留后 SKILL.md 真的出现」。
  • 每条新用例都做变异验证:把实现逐项改回旧行为(按技能名定位、停用改回改名、两个字段只写一个、漏掉 disable-model-invocation 的反向映射、enabled 不再派生、迁移读取报错条目名等 9 项),确认预期的那条用例真的变红。
  • ⚠️ 发布前的一次独立审查为本版拦下两条会毁掉体验的缺陷,都已修并补了回归用例:
    1. 一条技能就能让整个技能页打不开:我曾拒绝「目录名以 .md 结尾」的条目,理由是「宿主会把它当扁平技能」——理由反了,宿主是目录分支优先,odd.md/ 完全合法。后果是 list() 整体抛错,连健康技能都管不了。现已改为形态一律看磁盘(kind 只当提示),并让迁移读取那条支线单点容错。
    2. 旧机制残留的「启用」点了没反应:setSkillEnabled 改成只写字段后从不改名,而残留的停用状态在文件名里,于是点「启用」改的是那个 .disabled 文件的内容、宿主永远看不见(还报成功)。现在残留走 rename 恢复,并拒绝覆盖已存在的 SKILL.md。
      另修一处两个 RPC 结论相反:detail 从文件内容派生「已启用」,而 list 说「已停用」——残留的停用状态不在内容里。现在两处同一判据,并有用例钉住一致。
  • 审查还发现三处零覆盖(变异跑全量仍绿),已补用例:enabled 漏看 userInvocable(会把「仅用户可调用」误判成已停用)、detail 的 enabled 恒 true、迁移行的技能名取值。
  • 审查还指出一处文档承诺未兑现:三处文档曾用「详情页有两个独立开关」来论证停用代价可接受,而当时用户面只是一个只读显示。这一处后来不是改文案了事,而是把开关补上了(见上)。

真机复核

除了自动化用例,这一版还在一个隔离的 DSH 实例上真点了一遍(DSH_HOME 与 DSH_AGENTS_HOME 都指向临时目录,profile 用 junction 共享,所以加载的仍是本仓库代码;用完即删,不碰真实技能库)。合成技能库覆盖 7 种形态,逐项点过:

形态 结果
磁盘目录名 ≠ 技能名(disk-name/ 里写 name: alias-skill) 详情能打开、正文读得出
目录名以 .md 结尾(odd.md/) 正常列出,不再让整页报错
技能目录是 junction(本体在外部库) 正常
根级扁平单文件 正常
停用 → 启用(缺省态技能) 逐字节还原(哈希一致)
只点「用户可调用」开关 只新增一行 user-invocable: false,模型面未被动,列表立刻出现「不可手调」徽章
旧机制残留 列出 → 点「启用」→ 磁盘上真的改回 SKILL.md → 刷新后成为普通技能

两处如实说明的测试空白(都写在用例注释里,不是悄悄跳过):

  1. 本机建不了文件符号链接(Windows 上连 mklink 都要特权),而 realpath 会把大小写归一,所以「根下扫出的条目名」与「宿主所报路径的末段」在本机能构造出的任何形状里恰好相同,两者不可分辨。与其写一条永远跑不到的分辨断言(那只会制造「看起来测到了」的假象),用例改为断言可构造的那一半:扫盘无法证实文件在根下时,不填根、也不拿路径末段顶替。
  2. 条目名里的 :(Windows 上 NTFS 备用数据流语法)刻意不拦:POSIX 上它是完全合法的文件名、宿主照收,拦掉就会重犯上面第 1 条那种「宿主认可、面板操作不了」的缺陷;而放行它并未越过本层已有的边界(entry 仍是单段、仍限定在宿主报告过的根内)。这是权衡后的决定,写进了代码注释。

验收脚本自身也修了四处 bug(快照取在停用之后、peer 链接建在 npm install 之前、夹具正文与断言的名字不一致、以及用 watch: false 跑导致「恢复后重新发现」永远等不到失效)——记在这里是因为「验收脚本自己也要被怀疑一次」。

dsh-toolbox-ui v1.6.5

dsh-toolbox-ui v1.6.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 12 Oct 01:23

dsh-toolbox-ui v1.6.5

技能面的三个缺陷修复,都出在同一个形态上:技能目录是指向外部同步库的符号链接(把 ~/.dsh/skills/<名字> 做成链接,让技能本体放在别处版本管理——这是很常见的做法)。1.6.4 修好了这一形态的「显示」部分,但操作部分还有三处不可用,这一版补齐。

修了什么

停用一条技能后,它从面板消失了

链接形态下点「停用」:磁盘上确实改名成了 SKILL.md.disabled,但那条技能**既不在「已启用」也不在「已停用」**里——面板再也看不到它,也就点不到「启用」把它恢复。

根因是扫描已停用技能时用「目录项是不是目录」来分类,而符号链接目录在这里报的「不是目录」。于是链接技能一停用就掉进另一个分支、哪边都不匹配,被静默跳过。现在改按跟随链接之后的真相判定,停用后照常列在「已停用」里,一键启用回来。

「删除技能」报错

同一形态下删除会失败,报一个目标引用错误,看起来像技能删不掉。现在正常工作,而且只删掉那个链接——技能本体在外部同步库里的内容一个字都不会动(这条有专门的用例守着:删除是这套操作里唯一不可逆的一步)。

「扁平技能的文件本身是链接」

技能有两种形态:目录捆绑(<名字>/SKILL.md)和单文件(<名字>.md)。上一版只修好了「目录是链接」,单文件本身是链接这一种仍会定位不到——它的真实路径在外部库里,与技能根不在同一棵树下,所以任何「比较两个路径」的实现都判不出来。

这一版换成按宿主的定位规则判别:形态看技能文件名(SKILL.md 还是 <名字>.md),根看宿主报的 resourceBase——文件名在链接前后完全一致,所以四种形态(目录/单文件 × 普通/链接)一次全部正确,不需要为哪种链接开特例。

升级

dsh plugin --profile web update dsh-toolbox-ui
# 装完重启 dsh web

⚠️ 若你还在 1.6.1 及更早的版本,面板不会提示这一版(那些版本的更新检查读的是一条已经失效的数据源)。请手动执行上面这条命令升一次;从 1.6.2 起提示已恢复正常。

仍装着旧包(≤1.5.3)的话走迁移:面板点「升级」升到 1.5.5,再点「迁移」即可(它会装本包、卸掉旧包),详见 1.5.4 的说明。

兼容性:只改技能面,MCP 面与 1.6.4 完全一致。

验证

node --test → 544 通过 / 0 失败。本轮新增的用例不再用手写的替身数据,而是拉起真实宿主包(@deepseek-ai/dsh-skill + @deepseek-ai/dsh-skill-filesystem,后者本轮才加入开发依赖——此前它不在依赖里,所以整类「宿主两个路径字段口径不同」的缺陷都测不到)配合真实符号链接,并把断言落在操作真的改了盘上(列出几行、显示什么字都不算数)。

发布前另做前后对照:同一套「用户的完整形状」(DSH_HOME 落在工作区内 × 技能目录全是链接 × 两个工作区 × 11 条技能),对 npm 上的 1.6.4 原始产物与本次工作树分别跑同一份验收脚本——1.6.4 在「停用后是否还看得见」与「删除是否伤到外部库」两项上是 0/11,本次全部通过。回归用例也验过能拦下 1.6.4(用它替换回两个源文件即变红)。