DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Skills Manager — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins

@lolkda/dsh-skills-manager

Skills Manager

Manage the DeepSeek Harness skill catalog from Settings: list what DSH actually resolves, enable or disable a skill by policy without touching its source file, read and edit SKILL.md, and create, import, or permanently delete skills.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add @lolkda/dsh-skills-manager@0.2.2
READMECompatibilityVersions

Compatibility and provenance

Skills Manager is published as @lolkda/dsh-skills-manager and currently resolves to version 0.2.2. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
10/16/2026

Versions

0.2.2stable
9/24/2026
0.2.0stable
9/20/2026
0.1.0stable
9/17/2026

Related plugins

Loading related plugins…

Latest
0.2.2
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
215.2 kB
Files
19
Surface
web
License
MIT
Source
npm
Weekly downloads
58
View source ↗Project homepage ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in developer-tools.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Sdk Minimal@deepseek-ai/dsh-sdk-minimalThe standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, and JSONL sessionsSdk App@deepseek-ai/dsh-sdk-appThe dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-baseExperimental Inspector@deepseek-ai/dsh-experimental-inspectorExperimental cross-realm CDP hub for Host debugging and Client Runtime inspection

README

@lolkda/dsh-skills-manager

在 DeepSeek Harness 的 Web 界面里管理 DSH 自己会读取的技能目录。

它只做一件事,并且把它做透:让「这个技能现在到底会不会被加载」这个问题有唯一、可验证的答案。

  • 列表 —— 按 DSH 的真实根目录顺序列出技能,标明来源、rank、重名遮蔽关系。
  • 启停 —— 改变 DSH 里的调用策略,不改动源文件。随时可撤销。
  • 正文查看与编辑 —— 直接读写 SKILL.md,带 frontmatter 诊断。
  • 新建 / 导入 —— 目录 bundle、平铺 .md、ZIP 包。
  • 删除 —— 永久删除,带二次确认(没有回收站)。
  • Agent 工具 —— 模型也能列出、读取、创建、启停技能。

它管理哪些目录

与 @deepseek-ai/dsh-skill-filesystem 的默认根逐条对齐,因为策略覆盖必须落在文件系统提供方真正会读到的那些技能名上:

rank来源路径
100project-dsh<项目根>/.dsh/skills
200project-agents<项目根>/.agents/skills
300custom配置里的 customSkillDirs
400user-dsh$DSH_HOME/skills
500user-agents$DSH_AGENTS_HOME/skills
600bundled$DSH_BUNDLED_SKILL_DIR(只读)

项目根 = 含 .git 的最近祖先目录。刻意不聚合 cc-switch、Codex、Claude、Cursor 等其他 Agent 的目录 —— 那些不是 DSH 会读的东西,把它们混进这个列表只会让上表这个唯一答案变得含糊。

启停是怎么实现的

不改文件,是因为改文件既危险又不可逆:注释会被吃掉、未知的 frontmatter 键会丢失、用户自己写的 disable-model-invocation 会被抹掉,而且卸载插件后无法恢复。

实际做法是向 ctx.skills 注册一个覆盖提供方:

  • 只对被显式启停过的技能发出候选,rank 取 0(低于文件系统提供方的 100–600);
  • 注册表的规则是「同层内 rank 小者胜」,因此这些名字的裁决必然被覆盖候选赢得;
  • 候选的 invocation 就是用户要的策略,get() 仍返回真实正文,所以「启用」的技能照常可加载;
  • 取消覆盖,该技能立刻回到文件自己的声明。

关键细节:dsh-skill 的注册表按 scope 分层,而 agent preset 会再挂一次 dsh-skill-filesystem、注册进 preset 自己的层(见 dsh-agent-presets/presets/standard/agent.cordis.yml)。读取时「最近层直接赢得重名」,所以只在宿主层注册覆盖会被 preset 层压掉。本插件因此在宿主行和每个 agent 作用域各注册一次(ctx.on('agent/created') + agents.list(),经 agent.ctx.get('skills'))。

这条机制有测试为证:test/registry.test.mjs 挂的是真实的 dsh-skill + dsh-skill-filesystem,断言取自 ctx.skills.snapshot() —— 与模型最终看到的会话目录同源。

安装

dsh plugin --profile web add @lolkda/dsh-skills-manager@latest --registry=https://registry.npmjs.org/
dsh --profile web --dump-config   # 确认配置里出现 dsh-skills-manager

从本地源码迭代(宿主半边改动需要重载 profile):

dsh plugin --profile web add link:F:/project/dsh-skills-manager

标准 npm 安装包

源码可直接使用 npm pack 生成标准 npm tarball,不需要额外编译。安装包包含宿主 ESM 入口、浏览器客户端入口、DSH bundle patch、依赖声明、许可证和版本记录,不包含测试、审计日志或本地依赖。

npm pack
# 普通 npm 项目安装:
npm install /absolute/path/lolkda-dsh-skills-manager-0.2.2.tgz
# DSH Web profile 安装:
dsh plugin --profile web add /absolute/path/lolkda-dsh-skills-manager-0.2.2.tgz

替换已安装版本后需要重启 DSH 宿主,再刷新原 GUI;只刷新页面不会替换已加载的 ESM 后端。打包不会自动发布到 npm,@latest 指向的仍是注册表已经发布的版本。改动与已知测试限制见 CHANGELOG。

配置

在 profile 自己的 cordis.patch.yml 里用 id 定向 patch 覆盖(该层在每个 bundle 层之后应用):

- id: dsh-skills-manager
  name: '@lolkda/dsh-skills-manager'
  config:
    customSkillDirs: ['F:/shared-skills']
    bundledSkillDir: 'F:/dsh/bundled-skills'
字段默认含义
includeDefaultRootstrue是否包含项目根与用户根
customSkillDirs[]rank 300 的额外根
bundledSkillDir$DSH_BUNDLED_SKILL_DIRrank 600 的内置根,只读
dshHome / agentsHome$DSH_HOME / $DSH_AGENTS_HOME根目录解析基准
logtrue是否写 $DSH_HOME/dsh-skills-manager/dsh-skills-manager.log

状态落在 $DSH_HOME/dsh-skills-manager/state.json,只存显式覆盖,不缓存任何从磁盘推导出的内容。

验收:不信自报,读注册表

GET /dsh-skills-manager/registry 返回 ctx.skills.snapshot() 的真实解析结果 —— 每个技能最终赢得的 invocation、胜出提供方、来源。启停是否生效由它判定,而不是由本插件自己的状态接口自称:

curl -H "Host: 127.0.0.1:3080" http://127.0.0.1:3080/dsh-skills-manager/registry

响应里的 scope 字段说明它读的是哪一层,这一点很关键:

  • scope: "host" —— 没有任何活动会话,读到的是宿主层。真实部署里宿主层通常一条技能都没有:技能由 preset 层提供,而 dsh-skill 的候选来自 [layers.global, ...chainLayers(scope)]。
  • scope: "agent" —— 有会话,skills 是该 agent 所在层链的合并结果,也就是模型真正看到的那一份。host 字段仍然给出宿主层视图供对照。

读与写在作用域上是不对称的,写代码时几乎必然踩一次:

方向作用域从哪来
写 registerProvider从调用上下文推断(scopeOf(this.ctx))
读 snapshot / get只看 options.scope,不从上下文推断

所以「从 agent 的 ctx 上调 snapshot({})」看起来合理,实际读到的是 global 层的空结果。lib/scope.js 负责把这层差异收进一个地方。

活动日志里每个 agent 建立时会记一条 scope-snapshot,写明那个会话解析出的技能清单 —— 排查「为什么这个会话里没有它」看这里,而不是看插件自己的状态接口。

与 DSH 实际解析的一致性核对

/registry?sessionId=<当前会话ID>&cwd=<所选目录> 的 divergence 只对指定会话、与所选 cwd 匹配且观测完整的 agent 视图进行核对。它比较注册表合并后所有提供方的名称、可观测调用策略和来源;合法 overlay 接管不算缺失或来源冲突。没有匹配会话、cwd 未知或观测不完整时返回 checked:false,不借用其它会话冒充当前会话。

技能是否由本插件添加,不是核对依据。 同一个 cwd 可能对应多个会话,旧会话的技能视图可能为空;仅挑第一个同目录会话会把原生文件系统技能误报成「模型收不到」。设置页通过 DSH 的 useSessions / retainedBy.mainView 获取主视图选中的会话,传递其 ID 和 cwd,并在切换会话后丢弃旧响应。收到其它会话的响应(例如旧版后端忽略了 sessionId)时不显示核对结论。旧客户端未传 sessionId 时,后端仅在所选目录恰好有一个会话时核对;有多个会话则明确返回未核对。

为什么需要它:本插件与 dsh-skill-filesystem 各有一份配置(customSkillDirs、bundledSkillDir、includeDefaultRoots、dshHome、agentsHome),两边没有任何机制保证一致。分叉的后果是:

  • 我们多报 → 界面上有这条技能,模型从来收不到;
  • 我们少报 → 技能实际在生效,界面上看不见。

两种都不会报错,只能主动比出来。界面上一致时显示「已与 DSH 实际解析核对:N 条一致」,不一致时分别点名「多报了哪些(模型收不到)」与「少报了哪些(界面看不到)」;没有可用会话视图时什么都不显示,不假装核对过。

每个会话建立时,活动日志里的 scope-snapshot 也会带上这句结论:

agent session-... 的技能视图:共 9 条 —— apple-liquid-glass、grill-me(模型不可用)、...;与 DSH 实际解析一致(9 条)

覆盖字段的保留与已知暂缓项

覆盖提供方的候选会整条取代文件系统的候选(同层 rank 0 胜出),所以它必须把技能原有的字段 一并带过来。whenToUse 曾经漏掉:一条技能只要被启停过一次,它的 whenToUse 就会对所有下游 消费者消失 —— 界面看不出来,注册表也不报错,只是那条信息没了。现在原样带过来,test/layers.test.mjs 里有一条守门测试(去掉转发即失败)。

本插件保留技能原有的 metadata 对象,不往其中添加自己的标记。该对象通过显式声明的 yaml 依赖读取;真实注册表 get() 的前后对照见 backend-p2.test.mjs。这不等于全部 frontmatter 语义已与原生一致:用户明确暂缓的 R21(缩进分隔符导致正文变化)仍保留,详见 修复范围与暂缓记录。策略覆盖状态由 /catalog 返回的 overrides 给出。

Agent 工具的参数表只用 DSH 支持的 JSON Schema 子集

ctx.tools.register 会校验 output.schema,但完全不校验 parameters —— 参数表写错在注册期 一路绿灯,问题要到模型那边才显形。所以 test/tools.test.mjs 里直接用 DSH 自己的 assertSupportedJsonSchema 把七个工具的参数表逐个过一遍(用它的校验器,而不是照着源码重写一遍 规则:规则会随 DSH 变,重写的那份不会)。

DSH 支持的子集里 type 只能是单个字符串:type: ['boolean','null'] 会被直接拒掉,而且 jsonSchemaToTs 会把整份参数渲染成 unknown —— 在按 schema 渲染签名的模式下,模型看到的参数表 形同没有。因此 skills_set_enabled 的三种状态里,「清除覆盖」由省略 enabled 表达,而不是 传 null。

(内部 API 不受这个限制:runtime.setEnabled({ enabled: boolean | null }) 与界面用的 /policy 路由仍然用 null 表示清除。)

Agent 工具只写用户根

暴露给 Agent 的七个 skills_* 工具不接受 rootKey:新建与导入一律落在用户根 ($DSH_HOME/skills)。不该让模型随手挑一个根去写。要跨根操作,skills_set_enabled 会自己 找到当前胜出的那一条,并作用于它所在的根(包括项目根与只读的 bundled 根 —— 后者只允许启停, 不允许改文件)。参数表里 additionalProperties: false,多传的字段会被框架挡在门外。

loadable 的含义是「DSH 会不会真的加载它」

DSH 用真正的 yaml 库解析 frontmatter,任何解析失败都会让整条技能被丢弃。本插件是手写的 YAML 子集,所以对这类写法一律明确报错,而不是"能读出来就算数":

--- 
name: demo
description: 参考 macOS): 浅灰白底        # 冒号后跟空格 = YAML 嵌套映射 = DSH 整条丢弃
---

这样的技能在界面上标红「不可加载」、开关禁用,详情里给出原因和修法(把整个值用引号包起来)。 它也不参与同名仲裁 —— 既然 DSH 看不到它,它既不能胜出,也不能把后面真正会被加载的那条挤成"被遮蔽"。

理由很直接:这个插件最容易犯的错,是声称一条模型从未收到过的技能"生效"。 那比不做还糟。

路由前缀为什么不放在 /api 下:dsh-client-connection 用 {kind:'prefix', path:'/api'} 注册了一个鉴权路由,而前缀路由先注册先匹配;本插件的 bundle 排在 profile 末尾,于是所有 /api/* 请求恒定 401。@lolkda/dsh-prompt-manager 早已用非 /api 前缀绕开这一点。

从 michengai 迁移

@michengai/dsh-skills-manager 会聚合 ~/.cc-switch/skills、~/.codex/skills、~/.claude/skills 这些外部目录,其中一部分技能在 DSH 自有目录里并不存在。移除它之后那些技能会一起消失 —— 它们本来只是靠那个插件才可见。

把它们导入到 $DSH_HOME/skills 之后再移除,技能就不会丢:

node scripts/migrate-external-skills.mjs --dry-run   # 先看会做什么
node scripts/migrate-external-skills.mjs             # 默认不覆盖同名技能,可重复运行

脚本复用插件自己的 importDirectory,因此语义与界面上的「导入」完全一致:文档统一落成 SKILL.md,目录名取自 frontmatter 的 name(例如 apple-design-skill-project 会落成 apple-liquid-glass),附属文件原样保留。

开发

零构建步骤:宿主半边是手写 ESM JS,浏览器半边是客户端模块系统的手写懒 CJS 工厂,因此不存在「改了源码忘了构建」这一类失败模式。

node --test                      # 全部测试:单元 + 真实注册表集成 + 分层遮蔽 + 客户端契约
node spike/registry-probe.mjs    # 一次性机制探针:打印三个场景下的胜出者

改动宿主半边后需要重载 profile 才生效:

dsh plugin --profile web add link:F:/project/dsh-skills-manager

工程约定与已实测的机制细节见 research/DESIGN.md,真机验收证据见 research/ACCEPTANCE.md。

发布

推一个 v<package.json 版本> tag 即可,GitHub Actions 会跑测试、打包并发布到 npm:

git tag v0.2.2 && git push origin v0.2.2

正式版发到 latest,带 - 的预发布版发到 next,两者不会互相污染。认证走 npm Trusted Publishing(OIDC)—— 不需要任何长期 token 或仓库 secret;一次性配置、dry-run 演练入口和实测踩到的坑见 docs/release.md。

许可

MIT