Repository navigation
Releases: aawef/dsh-skill-manager-tags
Release list
2026-10-24 — 0.1.6:管理标签界面三处改进(技能搜索框 / 窗口定高 / 已有标签优先)
本次发布
v0.1.6· npm: https://www.npmjs.com/package/dsh-skill-tag/v/0.1.6
本节是 0.1.6 的发布说明,细节见下面三节。
- 「管理标签」右列新增技能搜索框(工作区版 / 对话版共用)。只剩一个输入框和
「清除」按钮,标题在有搜索词时变成技能 (匹配/总数),让人看得出是被过滤了。
搜索只过滤显示:view.visible、手动添加、禁用、继承、实际下发给模型的集合全都不变。
无匹配时给「没有匹配「xxx」的技能。」,与「本来就没有技能」区分开。 - 浮层高度固定为 82vh。第一版只写了
maxHeight,搜索越准、行数越少、窗口就越小,
每敲一个字母窗口抖一下。定高之后只有中间列表滚动,边框与底部按钮位置不动。 - 「skill 联系 tag」右列把该技能已有的标签排到最前面,稳定分区、组内保持原序,
和搜索可叠加;勾选态与写库内容都不受影响。
验证
三处各有渲染级测试:tests/__manage-render.mjs(13 项,含搜索过滤与高度不变)、
tests/__page-render.mjs(23 项,含排序与搜索叠加、ownedTagsFirst 纯函数)。
其余测试文件全绿;skilltags.test.mjs 中 5 条涉及 mkdtemp/spawn 的用例在本机
沙箱下以 EPERM 失败,与本次改动无关(未触碰 src/)。
dsh plugin --profile <your-profile> add dsh-skill-tag@0.1.6资产:dsh-skill-tag.tgz(sha256 ea61aa020a0ba2ee34ae879f7f9d8edee8101a9bdd66462a5cd6344d80eab65b)
稳定地址:https://github.com/aawef/dsh-skill-manager-tags/releases/latest/download/dsh-skill-tag.tgz
2026-10-24 — 0.1.5:解除对宿主内核包 `dsh-storage-domain` 的依赖
本次发布
v0.1.5· npm: https://www.npmjs.com/package/dsh-skill-tag/v/0.1.5
现象
DSH 升级到 0.2.1-rc.3 后,侧栏所有工作区与对话消失。数据本身没有丢
(~/.dsh/storages/workspace.json 与 ~/.dsh/sessions/** 都完好),是界面读不到。
根因
本插件此前 import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain"。
为了让这个裸导入能解析,安装时 profile 的 package.json 里必须再装一份
@deepseek-ai/dsh-storage-domain 实体 —— 而宿主 app.asar 里本来就内置了同名模块。
于是同一个进程里出现同一模块的两份不同版本:
- profile 里那份被钉在
0.2.1-rc.2; - 宿主升级成
0.2.1-rc.3后,运行时按includePrerelease的 semver 校验判定旧副本不兼容,
直接拒绝加载(见profiles/desktop/.plugin-manager/logs/*/pnpm.log); - 存储域整体未启动 →
ctx.storageDomain缺失 → workspace/session 列表为空。
同一结构问题在 0.2.0-rc.2 时代已经发生过一次,当时的处置是「把依赖从插件挪到 profile +
留一句升级时记得同步改版本号的人工提醒」。升级 rc.3 时没人同步 → 原样复发。只要插件还持有
宿主内核包的副本,每次宿主升级都必然再炸一次。
做法
- 新增
src/storage/domain-spec.js,本地实现defineDomain/domainTable:- 宿主的
defineDomain实际只做「校验 + 原样返回 spec」,domainTable只是{ valueSchema }
包装,复刻成本极低; - 校验规则与宿主一致(
UNIT_NAME_RE = /^[a-z][a-z0-9_]*$/、version 非负整数、
compatibleVersions低于 version、global.schema不接受null等)。
- 宿主的
src/storage/schema.js改为从本地模块引入;宿主能力仍走ctx.storageDomain.open(skillTagDomain),
这部分本来就没变。- 删除
peerDependencies["@deepseek-ai/dsh-storage-domain"]—— 插件对宿主内核包零 import,
不再需要任何声明。 engines.dsh由版本白名单(0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || >=0.2.1 <1.0.0)
改为半开区间>=0.2.0-rc.1 <1.0.0。
为什么改成半开区间就不会再漏
宿主校验用的是 semver.satisfies(runtimeVersion, range, { includePrerelease: true }),
预发布版本参与比较;并且插件只要写了 engines.dsh,peerDependencies 完全不参与判定
(宿主源码 unmetRequirements)。所以一个半开区间即可覆盖 0.2.1-rc.3、未来的 rc.4、
0.2.2-rc.1 以及 0.2.1 正式版,不必每次宿主发版都回来改白名单。
用宿主自带 semver 实测:
✅ >=0.2.0-rc.1 <1.0.0 vs 0.2.1-rc.3 / 0.2.1-rc.4 / 0.2.2-rc.1 / 0.2.1
❌ 旧白名单 vs 0.2.1-rc.3 (这就是被拒的原因)
升级建议
强烈建议从 0.1.4 升级到 0.1.5:0.1.4 在 DSH ≥ 0.2.1-rc.3 上会被判不兼容而跳过后台加载,
并可能连带让整个存储域无法上台。装完 0.1.5 后,profile 里不再需要
@deepseek-ai/dsh-storage-domain 这一行,可以删掉(若之前手动加过)。
验证
- 本仓库测试 194/194 通过;
- 宿主 profile 重装后
@deepseek-ai/顶层只剩cosmokit/schemastery两个通用库,
不再有任何内核包副本; - 升级后工作区与历史对话正常显示。
dsh plugin --profile <your-profile> add dsh-skill-tag@0.1.5资产:dsh-skill-tag.tgz(sha256 fb62c076472f1561ca449a6c82c7c16d1d9f6c5ee55c3916db8fb87cd66fe66f)
稳定地址:https://github.com/aawef/dsh-skill-manager-tags/releases/latest/download/dsh-skill-tag.tgz
2026-10-22(补)— 修复:`/被屏蔽的技能名` 会让界面卡死(provider.get 自递归)
本次发布
v0.1.4· npm: https://www.npmjs.com/package/dsh-skill-tag/v/0.1.4
现象
用户敲一轮 /<某个被屏蔽的技能>(例如 /office-docx),agent 的 pre-step 永不 resolve,
界面像卡住一样,没有任何报错。
根因
userInvocable:false 并不能挡住这条路径 —— dsh-tool-skill 的 agent/pre-step 对
/name 手势是先 ctx.skills.get(name) 再判 isUserInvocable(宿主源码 line 909)。
而宿主 registry.get() 的流程是:
collect() → 选中胜出候选(agent 层里就是我们的影子)→ 调影子自己的 provider.get()
我们的 provider.get() 原来会 upstream.getDefinition() → 又回到 registry.get(name)
→ 又选中影子 → 又是我们 …… 每轮都是 await,栈不涨、Promise 永不 settle,
所以表现为静默挂死而不是爆栈。实测探针里 registry.get 被反复调用到人为设的上限。
补一句:模型侧走 skill 工具不会撞上这个坑 —— 那边先 list() 找 summary,
影子 modelInvocable:false 会直接抛 not available for model invocation(line 874-875)。
只有 /name 手势(人打的)才先 get。
改动
lib/index.js createFilterProvider.get():被 denied 命中的名字就地返回 deny 定义
(content: ""),不再回问宿主。本来影子也没有正文可给,所以对外行为不变,只是断掉回环。
测试
- 新增两条:get 被屏蔽的名字时上游
getDefinition/summaries调用次数必须为 0,
且返回的定义能过宿主validateDefinition的形状校验;放行的名字照旧拿到真正文。 - 全量 194 通过 0 失败。
- 探针(
probe-get-loop.mjs):修前registry.get调用 6 次(人为截断),修后 0 次。
dsh plugin --profile <your-profile> add dsh-skill-tag@0.1.4资产:dsh-skill-tag.tgz(sha256 90b31c9e8bd10e0bedc37a9ecd0f4504c64922948cba39c8a54160214e9a2162)
稳定地址:https://github.com/aawef/dsh-skill-manager-tags/releases/latest/download/dsh-skill-tag.tgz
2026-10-22 — 修复:包/插件带来的技能永远屏蔽不掉
现象
模型侧技能目录 71 条,插件口径 universe 105 / visible 54 / denied 51。
按名字对账:71 − 17 = 54 = visible,恰好 17 个该屏蔽的技能原样漏给模型。
漏网名单干净得可疑 —— 全部是"不由文件系统技能根提供"的那些:
- aegis 4(using-aegis / update-aegis / writing-skills / communicating-concisely)
- mattpocock 4(ask-matt / research / teach / writing-for-agents)
- archify 1、本插件的 tagging-skills 1、office 7(
path为 null)
被成功屏蔽的 34 个则全部来自 ~/.dsh/skills + ~/.agents/skills。
日志、右列界面、/skill-tag/api/skills 都是对的(105),只有模型侧少影子 ——
所以这是静默失效,长期没被发现。
根因
createFilterProvider.list() 只遍历 upstream.summaries() 的结果发影子,
即发射范围 = 本次能看到的 summary。而 createUpstreamAdapter.summaries() 在
nesting.depth > 0 时会整段跳过宿主快照,只剩扫盘结果;守卫之所以被占住,是因为
apply() 里 createUpstreamAdapter 与 createSkillDiscoverer 共用同一份
snapshotGuard —— 设置页 / UI 快照 / 另一个 agent 的收集 / 并发刷新任一在飞,
否认路径就被连坐降级。
于是"看得见"和"看不见"的分界线,恰好就是"文件系统根"和"别的 provider"的分界线。
改动
lib/index.js:
- 影子按名字发(根治):
list()分两段 —— 看得见的照抄 summary(保留
path/ 描述);看不见的直接构造最小合法候选。屏蔽按键名生效,不依赖可见性。 - 两个出口各用一份重入计数器:递归那圈只在 adapter 内部,
createSkillDiscoverer不在回路里,共用只会互相连坐;发现器另加同 key 并发合并。 - 观测(方案 C):发生补发时 warn 一行
shadow backfill ... backfilled=N,
降级时附hostSnapshot=skipped(nested);健康路径不刷日志。
顺带修正:
- 影子
rank统一取 0。原来照抄上游 rank,office 是BUNDLED_SKILL_RANK=600,
同层里若有 rank 更小的同名候选就会被顶掉(层内按 rank 升序去重)。 - 上游
summaries()抛错时不再return [](那等于把全部技能放行),改为只用
denied补影子。 denied里的历史脏名字先过宿主的 kebab-case 校验:一个不合法候选会让
整个provider.list()抛错,宿主随即丢弃本插件的全部贡献。