🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

技能开关面板 twenty-3rd/dsh-skill-switch

DeepSeek Harness
twenty-3rd27 天前有提交已提供中文文档扫描:含敏感能力

一键屏蔽或恢复项目技能,并清理全部副本

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/8 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add twenty-3rd/dsh-skill-switch
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · market
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 27 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-skill-switch(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/9 17:35:59

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 25 处,展示前 15 条)
  • 读取本地文件
    lib/index.js:233
    entries = await readdir(dir, { withFileTypes: true });
  • 读取本地文件
    lib/index.js:266
    await readdir(base);
  • 读取本地文件
    lib/index.js:278
    mode = parseMode(await readFile(join(base, "mode"), "utf8"));
  • 写入本地文件
    lib/index.js:369
    await mkdir(join(base, activeDir), { recursive: true });
  • 写入本地文件
    lib/index.js:372
    await writeFile(target, switchNote(blocked, projectRoot, mode), "utf8");
  • 读取本地文件
    lib/index.js:413
    entries = await readdir(dir, { withFileTypes: true });
  • 删除本地文件
    lib/index.js:420
    await rm(target, {
  • 删除本地文件
    lib/index.js:428
    await rm(dir, {
  • 读取本地文件
    lib/index.js:435
    if ((await readdir(base)).length === 0) await rm(base, {
  • 删除本地文件
    lib/index.js:435
    if ((await readdir(base)).length === 0) await rm(base, {
  • 删除本地文件
    lib/index.js:445
    await rm(path, {
  • 读取本地文件
    lib/index.js:456
    await stat(path);
  • 删除本地文件
    lib/index.js:1039
    await rm(withinDir, {
  • 删除本地文件
    lib/index.js:1045
    await rm(withinPath, {
  • 发起网络请求
    lib/client.js:86
    response = await fetch(`/skill-switch/api/${method}`, {
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-atomic-write@deepseek-ai/dsh-home-paths@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-session@deepseek-ai/dsh-skill@deepseek-ai/dsh-skill-filesystem
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 一键屏蔽或恢复技能
  • 全局删除技能副本
  • 技能有效性判定

DeepSeek Harness 项目级 Skill 开关:一键屏蔽/恢复、全局删除、有效性判定。

README

由 DeepSeek 最新模型翻译生成
dsh-skill-switch ⭐ 花 5 秒点一下 Star → <https://github.com/twenty-3rd/dsh-skill-switch> (打开后点页面右上角 ☆ Star → ★ Starred) 这个插件干的事很小:让你在会话里 5 秒关掉一个用不到的 skill,把散在四五个根里的副本一次删干净,并且告诉你某条 skill 到底是"有效"还是"为什么没生效"。 它替你省下的时间,值得还它一次点击。 DSH(DeepSeek Harness)的项目级 skill 开关 + 全局删除插件。在会话里多出一个「Skill 开关」标签页,用一张表把当前项目的 skill 列清楚,然后: - 一键开关:某条 skill 在本项目里是否可见,点一下就生效(写/删 <项目根>/.dsh/skill-switches/ 下的开关文件)。 - 删除全部副本:把一条 skill 在所有已知根里的副本一次删干净,删完面板与 runtime 目录里都不再出现。 - 有效 / 错误判定:每条 skill 都标出它对这个会话是否"有效"—— 有效 = A 在 skill 注册表里 ∧ B 模型可主动调用 ∧ C 用户可显式调用;任一条不成立就是「错误」,并把是哪一条(以及缺哪个 frontmatter 字段)写在行内。
展开完整文档(README 剩余 1 节)
dsh-skill-switch

⭐ 花 5 秒点一下 Star → <https://github.com/twenty-3rd/dsh-skill-switch> (打开后点页面右上角 ☆ Star → ★ Starred)

这个插件干的事很小:让你在会话里 5 秒关掉一个用不到的 skill,把散在四五个根里的副本一次删干净,并且告诉你某条 skill 到底是"有效"还是"为什么没生效"。
它替你省下的时间,值得还它一次点击。

DSH(DeepSeek Harness)的项目级 skill 开关 + 全局删除插件。在会话里多出一个「Skill 开关」标签页,用一张表把当前项目的 skill 列清楚,然后:

- 一键开关:某条 skill 在本项目里是否可见,点一下就生效(写/删 <项目根>/.dsh/skill-switches/ 下的开关文件)。
- 删除全部副本:把一条 skill 在所有已知根里的副本一次删干净,删完面板与 runtime 目录里都不再出现。
- 有效 / 错误判定:每条 skill 都标出它对这个会话是否"有效"—— 有效 = A 在 skill 注册表里 ∧ B 模型可主动调用 ∧ C 用户可显式调用;任一条不成立就是「错误」,并把是哪一条(以及缺哪个 frontmatter 字段)写在行内。
官方 provider 会整条丢弃的 skill(缺 name/description、YAML 坏掉)因此仍然看得见、看得懂——这是相对 dsh-skills-manager 的可见性优化。
- 详情视图:点开一行看这个名字在磁盘上到底有几处副本(根、文件、rank、会不会被加载、能不能删、每处各自的 frontmatter 问题)。

| 版本 | 0.4.0(历史见 CHANGELOG 与版本历史) |
| 许可 | MIT(LICENSE) |
| 插件形态 | DSH 标准双半体:host exports["."] → lib/index.js(ESM)+ client exports["./client"] → lib/client.js(浏览器 CJS 闭包工厂),dsh.bundle.patch → cordis.patch.yml |
| 实测环境 | DSH Desktop 0.2.0-rc.2 · macOS arm64 · Node ≥ 20 |
| 测试 | pnpm test 172 项;装进 profile 后 pnpm verify:installed 25 项 |

这是 v1(dsh-skill-switch 0.1.x,纯文件协议、无界面)的 v0.4。v1 的开关文件协议与屏蔽语义一字不改,升级不需要迁移任何已存在的开关目录。

为什么需要它

DSH 的 skill 是全局发现 + 会话注入的:装了的 skill 对所有会话可见。SKILL.md 的 category/paths 之类"限定范围"的元数据并不存在,所以"这个项目用不到 A 股分析器"只能靠外部开关表达。本插件补上这一层,并把它做成可以在页面里操作的东西。

安装(DSH Desktop 标准安装支持)

本包就是标准形态的 DSH 插件:用官方 dsh plugin 装,不需要手工改 profile、不需要注册任何全局路径。

标准安装用它自己的哪几个部位

| 标准安装要求 | 本包对应物 |
| package.json → dsh.bundle.patch 指向组合补丁 | ✅ ./cordis.patch.yml(insert 一行 skill-switch) |
| host 半体 ESM 入口 | ✅ exports["."] → lib/index.js |
| client 半体声明 | ✅ dsh.client{ inject, platform: "web" } + exports["./client"] → lib/client.js |
| 安装时不需要构建(pnpm ≥ 10 默认不给 git 依赖跑构建脚本) | ✅ lib/ 随仓库提交(lib/index.js、lib/client.js、lib/types/) |
| 客户端只依赖平台模块 | ✅ 构建期纯度闸门强制(跨插件协作走 cordis 服务) |
| 卸载后 profile 干净 | ✅ 只往 dsh.profile.bundles 里加一项;dispose 时摘除 ctx.skills 包装 |

dsh plugin --profile desktop add … 做的三件事:初始化 profile → 在 profile 目录里执行 pnpm <你的参数> → 成功后把 manifest 里带 dsh.bundle.patch 的依赖追加进 dsh.profile.bundles。

前置要求

| 项 | 要求 | 说明 |
| DSH Desktop | 0.2.0-rc.2 实测 | 面板用到 ctx.agents / snapshot({ scope }) / conversation.view 座位 / WebServer,在这个版本上端到端验证过 |
| Node | ≥ 20 | engines.node |
| pnpm | 与 profile 一致的大版本(本机 App 声明 11.7.0) | dsh plugin 把参数转发给 PATH 上的 pnpm;大版本不一致会撞 ERR_PNPM_UNEXPECTED_STORE |
| 平台 | macOS arm64 实测;代码无平台特定 | 路径只用 node:path,目录监视由官方 provider 负责 |

安装命令

1) 从 GitHub(公开仓库;lib/ 已随仓库提交,装完不用构建)
dsh plugin --profile desktop add -w github:twenty-3rd/dsh-skill-switch
dsh plugin --profile web add -w github:twenty-3rd/dsh-skill-switch

2) 从本地 checkout(开发/自用)
dsh plugin --profile desktop add -w /absolute/path/to/dsh-skill-switch
dsh plugin --profile web add -w /absolute/path/to/dsh-skill-switch

--profile 换成你的目标 profile:DSH Desktop 用的是 desktop,命令行启动用 web。
本插件两种都支持(package.json 的 dsh.marketplace.profiles 声明为 ["desktop", "web"],与本节示例一致——插件市场会据此判定能否一键安装)。

本包只支持这两种来源(GitHub 仓库 / 本地路径),不提供 npm 安装。
装完必须重启 DSH(新增 bundle 改变 host 侧组合,只刷新页面不够),重启后视图标签条里会出现「Skill 开关」。

怎么确认装对了

结构面:profile 只应有两处变化
cat ~/.dsh/profiles/desktop/package.json
dependencies 里多一条 "dsh-skill-switch" dsh.profile.bundles 里多一项 "dsh-skill-switch" cordis.patch.yml / pnpm-workspace.yaml / cordis.yml 不应有任何变化

运行面(仅源码 checkout 有;scripts/ 不进发布包)
cd /path/to/dsh-skill-switch
pnpm install && pnpm verify:installed # 25 项:产物能挂载、屏蔽/删除/补齐走真实 HTTP、
客户端产物符合 __ModuleLoader__ 契约、dispose 真摘包装

两条都过之后再重启 App,标签条里就会出现「Skill 开关」(不重启的话,结构面能确认,面板不出现)。

卸载

dsh plugin --profile desktop remove -w dsh-skill-switch
重启 DSH

面板与 /skill-switch/api/ 路由随 fiber dispose 一起消失,ctx.skills 的包装会被摘回原函数(有回归用例固定住"dispose 之后 off/<name> 不再隐藏")。

使用说明

三步:打开任一会话 → 点视图标签条里的「Skill 开关」→ 用行内开关切换。
面板按会话 cwd 所属的项目生效;切换立刻写盘,变化在下一个模型轮次生效,不需要重启。

界面

会话视图标签条里多出第三个标签(对话 / 轨迹 / Skill 开关;若装了 dsh-skills-manager,它排在 Skills 管理器之后)。面板由三部分组成:

「Skill 开关」面板:左侧筛选 chip 与搜索框,每行是来源/有效-错误徽标、描述与行内开关 +「操作」菜单

- 左侧筛选 chip:全部 / 已屏蔽(各自带计数),外加名字+描述搜索。
- 卡片左侧:名字、来源徽标、有效/错误徽标、描述,以及一行事实(判定失败的是 A/B/C 哪一条、frontmatter 缺什么、名字取自目录名、副本数量、名字不合法无法开关等)。
- 卡片右侧:一个纯 CSS 开关键(点一下就是一次切换)+ 「操作」下拉菜单。
- 操作菜单:「补齐 frontmatter」(仅 frontmatter 有问题的项)与「删除(全部副本)」;两者都有二次确认,且会把将受影响的文件路径一条条列出来。

界面语言:面板文案跟随 DSH 的界面语言,内置中文 / English 两套字典(键值一一对应)。
切换入口是 设置 → 通用 → Language(dsh-client-locale 提供的那一行);切换即时生效、不用刷新页面,「Skill 开关」标签文字也会一起变。面板自身没有独立的语言开关。

点开一行 → 详情(只读)

点卡片左侧的事实区(名字 / 描述)进入详情页,「返回列表」回到列表(筛选与搜索词保留)。
开关键与「操作」按钮不在事实区里,点它们不会跳走(名字不合法、不能写开关的行也照样能看详情)。

详情视图:描述、判定依据(条件 A/B/C 逐条 是/否)、存在的根位置(每处副本的根 / 文件 / 形态 / rank / 是否加载)

详情只做"把列表里被折叠掉的事实摊开":列表每行只显示胜出副本(rank 最小者),同名 skill 散在共享根 / DSH 根 / 库根里时,详情是唯一能看到另外几处的地方——包括每处副本各自的 frontmatter 问题(同一个名字在不同根里状态可能不同)、会不会被 DSH 实际加载、能不能删。它不带任何写动作(屏蔽 / 删除 / 补齐仍在列表里)。

顶部不再显示项目绝对路径、开关目录与 mode:那是内部实现细节,用户无法据此行动(开关目录的语义仍在「恢复本项全部」的二次确认里说明)。

功能一:项目级屏蔽

开关放在项目自己的 .dsh/skill-switches/ 里,随仓库走、随项目生效:

<项目根>/.dsh/skill-switches/ ├── mode # 可选:第一行 "deny"(默认)或 "allow" ├── off/<name> # deny 模式:这些 skill 被隐藏 └── on/<name> # allow 模式:只有这些 skill 可见

- 项目级:同一个 host 进程里,A 项目关掉的 skill 在 B 项目照常可见。
- 会话内生效:变化在会话的下一个轮次自动反映到 skill 目录——dsh-tool-skill 每轮 agent/pre-step 用 {cwd: session.header.cwd} 调 snapshot,digest 变化即重写系统提示里的目录,无需重启。
- 彻底隐藏:被关掉的 skill 不仅从目录消失,skill 工具按名加载也会被拦截。

面板里的「屏蔽/启用」在两种模式下语义一致(是否隐藏):

| 模式 | 屏蔽 | 恢复 |
| deny(默认) | 建 off/<name> | 删 off/<name> |
| allow | 删 on/<name> | 建 on/<name> |

两种情况下都会顺手清掉另一侧的同名残留,避免手工切换模式后语义漂移。

面板右上角的「恢复本项全部」清空 off/ 与 on/;如果项目当前是 allow (白名单)模式,它会同时移除 mode 文件——只清 on/ 会让白名单变成空集,等于把整个 skill 目录清空,与「恢复默认可见性」正好相反。确认框里会写明这一点。

仍然可以完全不用界面,直接建文件(与 v1 完全一致): sh
cd <你的项目>
mkdir -p .dsh/skill-switches/off
echo "这个项目用不到" > .dsh/skill-switches/off/ai-supply-chain-bottleneck-hunter
touch .dsh/skill-switches/off/api-design.md # .md 后缀也认
rm -rf .dsh/skill-switches # 全部恢复

功能二:全局删除

「删除(全部副本)」会把该名字在每一处已知根里的副本都删掉:

| 根 | 来源 id | 是否可删 |
| <项目根>/.dsh/skills | project-dsh | ✅ |
| <项目根>/.agents/skills | project-agents | ✅ |
| customSkillDirs | custom | ✅ |
| $DSH_HOME/skills(~/.dsh/skills) | user-dsh | ✅ |
| $DSH_AGENTS_HOME/skills(~/.agents/skills) | user-agents | ✅(可用 allowSharedRootWrites: false 变只读) |
| $DSH_BUNDLED_SKILL_DIR | bundled | ❌ 随宿主应用分发,删了会破坏安装 |
| $DSH_HOME/skill-library | library | ✅(dsh-skills-manager 的规范副本) |
| 其它 provider 注册的虚拟 skill | runtime / 任意 | ❌ 磁盘上没有可删的东西 |

范围要说清楚:本插件扫描的根由「当前会话 cwd 的项目根 + 用户级/共享/内置/库」组成,所以别的项目的项目级副本(<另一个项目>/.dsh/skills)不在扫描范围内——面板上按的是「删除(全部副本)」而不是「删除(全局)」,确认框里也会写明这一点。
要在别的项目里也消失,去那个项目的会话里再删一次(或直接删那个目录)。

安全边界:

- 路径全部由服务端扫描推导,客户端只能传名字;每个待删路径都要通过「严格位于所属根之内、且不是根本身」的校验,越界直接 403 forbidden。
- 一次删除里单点失败不会拖垮其它副本;全部副本都不存在时报 404。
- 请求里的 cwd 只在宿主确实认识该 session、且它的 cwd 还没 hydrate 时才被当作兜底;sessionId 不认识时一律用宿主进程 cwd。否则任何调用方都能拿一个不存在的 sessionId 加任意绝对路径,让写入/删除发生在别处。
- 方法派发走 Object.hasOwn:constructor / toString 这类原型成员会被当成「未知方法」返回 404,不会命中原型链。
- /skill-switch 的 error.message 一律是面向机器的英文(wire 层);面板按 wire code 出本地化文案,所以中文界面不会混进英文细节、英文界面也不会混进中文。
- 删除成功后会顺手清掉本项目里该名字的开关文件,避免「删了再装回来还带着旧屏蔽」。

配置

在 profile 的 cordis.patch.yml 里按 id 覆盖: yaml
- id: skill-switch
config:
switchesDir: .dsh/skill-switches # 相对项目根的开关目录 defaultMode: deny # deny | allow cacheTtlMs: 1000 # 状态缓存兜底时长(mtime 指纹命中则即时失效) forceRefreshOnGet: true # get() 拦截路径是否绕过缓存 dshHome: '' # 默认 resolveDshHome()($DSH_HOME 或 ~/.dsh) agentsHome: '' # 默认 $DSH_AGENTS_HOME 或 ~/.agents
customSkillDirs: [] # 额外 skill 根(同官方 provider 的 custom 层)
bundledSkillDir: '' # 默认 $DSH_BUNDLED_SKILL_DIR allowSharedRootWrites: true # false = ~/.agents/skills 只读(不动 Claude Code 的共享根)

注意点:判定「有效 / 错误」的口径

有效 = A 在目录里 ∧ B 模型可主动调用 ∧ C 用户可显式调用;任一条不成立 = 「错误」,且面板会写出是哪一条:

| 失败项 | 含义 | 面板文案 |
| A not-in-registry | 这个会话的 skill 注册表里没有它 → DSH 不会加载 | 不在 skill 注册表里(DSH 不会加载它) |
| B model-not-invocable | invocation.modelInvocable === false(disable-model-invocation: true) | 模型不能主动调用(disable-model-invocation) |
| C user-not-invocable | invocation.userInvocable === false(user-invocable: false) | 用户不能显式调用(user-invocable: false) |

A 必须按会话的观察者作用域读。 SkillRegistry.snapshot() 的 scope 决定它读哪些 layer(官方注释:"omitted reads the global layer alone");桌面 profile 里顶层 skill-filesystem 是 disabled 的,真正的 provider 注册在 agent preset 的 standing scope 上。所以面板用 ctx.agents.get(sessionId) 拿到该会话的 agent(在 DSH 里 agent 对象本身就是它的 ScopeKey:scopeTarget(agent, agent)),再以
snapshot({ cwd, scope: agent }) 读取——否则只能读到全局层(实测 37 行里只剩 3 条
内置 skill),把每个用户 skill 都误判成「错误」。

拿不到活跃 agent(归档/未 hydrate 的会话)或目录读取失败时,面板不显示判定列, wire 上也不产出任何错误项:把"我不知道"渲染成"它是错的"是欺骗。

为什么官方会丢弃某些 skill(A 失败的解释器)

@deepseek-ai/dsh-skill-filesystem 的 parseSkillFile() 要求 frontmatter 同时具有合法的 kebab-case name 与非空 description,否则整条丢弃。后果是这类 skill 在任何只看 runtime 目录的面板里都是隐形的——用户既看不到,也无从修。

本插件按与官方 provider 相同的根与 rank 扫描磁盘,但对每条候选做容错解析:

| 情况 | 官方 provider | 本插件 |
| 缺 name | 丢弃 | 列出,名字回退到目录名/文件名 |
| 缺 description | 丢弃 | 列出,描述回退到正文第一段有意义的文字(跳过代码围栏) |
| 无 frontmatter | 丢弃 | 列出,正文照常作为描述来源 |
| YAML 解析失败 | 丢弃 | 列出,且仍能从围栏之后取正文 |
| 名字不是 kebab-case | 丢弃 | 列出,标 invalid-name,并禁用开关(写不了开关文件) |
| name/description 不是字符串(如数字) | 丢弃 | 列出,按官方口径报 missing-name / missing-description |
| invocation 字段非法或用了遗留键 | 丢弃 | 列出,标 invalid-invocation |

判定口径是逐字对齐官方 parseSkillFile() 的:只有非空 string 才算字段存在(不 trim、不把数字转字符串),disable-model-invocation / user-invocable 只认 boolean / 1 / 0 / true / false / yes / no / on / off,出现 disableModelInvocation 一类的遗留键即视为整条非法。

这些 issues 不再是独立徽标,而是 A 失败的证据:一行显示成错误 · 不在 skill 注册表里(DSH 不会加载它) · frontmatter 缺少 description。
一行只说"错误"、不说为什么,是不可行动的;而上一版在凭据不足时说的「未生效 · 原因未知」,已经把"我没读到"当成了结论——两者都去掉了。

「补齐 frontmatter」仍然只改 frontmatter(没有就插到文首,坏的整段替换),正文一字不动;修复后 issues 清空、判定转「有效」。

架构

host 半体 (lib/index.js) ├── installSkillFilter() 装饰 ctx.skills 的 snapshot/list/get │ snapshot/list -> 原始收集 -> 按会话 cwd 解析项目根 -> 读开关目录 -> 过滤 │ get(name) -> 先查开关状态 -> 隐藏项直接 undefined -> 否则穿透原 get └── /skill-switch/api/ 面板 JSON 接口(POST,浏览器信任栅栏 + 根内路径约束) panel.load / switches.set / switches.reset / skills.delete / skills.repair

client 半体 (lib/client.js) └── conversation.view 座位上的「Skill 开关」标签(会话作用域,请求作用域骑在座位上)

- 开关状态缓存采用 mtime 指纹 + TTL 双条件:通常即时失效(现代文件系统 mtime 纳秒级),粗粒度 mtime 的文件系统由 TTL 兜底(默认 1 秒)。
- 所有文件系统错误降级为透传 + warn 日志,绝不影响 skill 系统本身。
- 卸载(fiber dispose)真的会摘除包装、恢复原始方法:teardown 是登记成 cordis
的 disposer(ctx.effect(() => () => {…})),不是写在 effect body 里——后者会在
安装瞬间就"卸载"并永远泄漏包装。恢复按包装前捕获的函数值赋值,不依赖 === 比较(cordis 每次读取服务属性可能给出不同的绑定代理)。
WRAP_TAG 只在卸载时清,所以 HMR/重复加载的双重包装保护在整个生命周期内有效。
真实 cordis 的回归用例覆盖了"dispose 之后 off/<name> 不再隐藏"。
- 路由栅栏与 /api 网关同规则(Host loopback 或 connection 行的 trustedHosts,跨站标记拒绝),是 DNS-rebinding/CSRF 防御,不是认证。
- 面板请求走未包装的注册表,所以被屏蔽的 skill 仍然带着 inCatalog: true 显示——「已屏蔽」和「根本不存在」不会被混为一谈。

已知边界

- 过滤按 skill 名字匹配,不区分来源(用户级、项目级、插件自带、runtime 注册的同名 skill 一视同仁)。
- 开关变化在下一个模型轮次生效(目录重渲染机制自身的语义),不追溯修改已注入的历史消息。
- 删除/补齐之后,SkillRegistry 的目录缓存由 provider 的文件监视器刷新(dsh-skill-filesystem 的 watch 默认开启)。面板自身的事实来自磁盘扫描,所以判定与 frontmatter 事实当场就正确;如果部署里把 watch 关掉了,判定里的 A (在不在注册表)要等到下一次目录重建才跟上。
- 判定里的 A 依赖活跃 agent:会话归档/未 hydrate 时面板不显示判定列,而不是把每一行都说成错误——宁可不说,也不说错。
- 升级到 0.3 需要重启宿主(host 半体是启动时加载的)。过渡期若只刷新页面而没重启,客户端对缺失的 errors 字段按空处理,不会白屏。
- skill-library 根不参与 runtime 目录(官方 provider 不扫它),所以库里的 skill 会以「未分配」出现;它仍然会被「全局删除」一并清掉,这正是「删干净」的一部分。
- 删除与补齐都走库语义:fs.rm 对符号链接只摘链接本身(不穿透删目标内容), writeFileAtomic 是同目录临时文件 + rename(替换链接本身)。根内的链接不会造成越界删除,这一点有专门的测试固定住。
- 「删除(全部副本)」不覆盖其它项目的项目级副本(见上文「范围要说清楚」)。
- 需要 host 重启后插件才初次加载;之后的开关变化无需再重启。
- 面板的语言偏好不是本插件的设置项:它由 dsh-client-locale 持有,只在 loopback 页面写入 $DSH_HOME/cordis.patch.yml;非 loopback 页面上的选择只保留在当前进程内。

版本历史

| 版本 | 内容 |
| 0.4.0 | 详情视图(点一行看每处盘上副本的根/文件/rank/可删性)、「返回列表」移到行右端、lib/ 与 README 面向发布整理 |
| 0.3.0 | 「有效 / 错误」判定(A ∧ B ∧ C)+ 按会话观察者作用域读注册表;删除「未生效」徽标与筛选面 |
| 0.2.0 | host + client 双半体面板:会话标签页、一键开关、全局删除、补齐 frontmatter;135 项测试 |
| 0.1.0 | 首个版本,纯文件协议:.dsh/skill-switches/{mode,off/,on/} 装饰 ctx.skills |

逐条变更见 CHANGELOG.md。

支持这个项目

最小的一步、最大的作用 —— ⭐ 点这里 Star,5 秒(页面右上角 ☆ Star → ★ Starred)。它同时做了三件事:

- ⭐ 对你自己:下次重装 DSH、换机器、排查「skill 怎么又冒出来了」,它就在你 Star 过的列表里,一眼找得回来,不用再搜、不用再翻聊天记录。
- ⭐ 对这个插件:Star 是「还有人在用」最便宜的信号。有 Star,我愿意继续跟 DSH 版本、继续修 bug、继续放宽边界;一颗都没有,它就自然烂在角落里——这是开源项目最真实的维护动力来源。
- ⭐ 对下一个人:DSH 的 skill 管理几乎没有现成方案,多一颗 Star 就多一分被人搜到、少踩一个坑的机会。

其余同样欢迎:

- 🐛 报问题 / 提需求:<https://github.com/twenty-3rd/dsh-skill-switch/issues> —— 安装踩的坑、判定口径的疑问、边界想放宽的地方,都欢迎开 issue;只来一句「我在用」也很有价值。
- 📣 顺手转给一个也在用 DSH 的人:DSH 生态还小,一次转发的实际效果比在任何地方分发都大。
- 发现 README 说法与代码不一致(判定口径、已知边界),直接指出即可;本项目的原则是「宁可不说,也不说错」,这类反馈优先级最高。

License

MIT,见 LICENSE。
预览分享卡片长什么样 ↓twenty-3rd/dsh-skill-switch
分享 · DeepSeek Harness Hub
twenty-3rd/dsh-skill-switch

一键屏蔽或恢复项目技能,并清理全部副本

这是你的仓库?认领条目

回到顶部