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.

DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins

dsh-skill-tag

Skill Tag

Tag DSH skills and use a single Boolean expression to select the subset of skills to send to the model at the workspace and conversation levels

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

npx -y @deepseek-ai/dsh plugin --profile web add dsh-skill-tag@0.1.15
READMECompatibilityVersions

Compatibility and provenance

Skill Tag is published as dsh-skill-tag and currently resolves to version 0.1.15. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
11/4/2026

Versions

0.1.15stable
11/3/2026
0.1.13stable
10/31/2026
0.1.12stable
10/31/2026
Show 10 more versionsCollapse versions
0.1.10stable
10/31/2026
0.1.8stable
10/28/2026
0.1.7stable
10/27/2026
0.1.6stable
10/24/2026
0.1.5stable
10/24/2026
0.1.4stable
10/22/2026
0.1.3stable
10/22/2026
0.1.2stable
10/21/2026
0.1.1stable
10/21/2026
0.1.0stable
10/21/2026

Related plugins

Loading related plugins…

Latest
0.1.15
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
531.8 kB
Files
35
Surface
web
License
MIT
Source
npm
GitHub
★ 0
Weekly downloads
1,292
Last push
11/3/2026
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 agents-orchestration.

Headless@deepseek-ai/dsh-headlessThe dsh one-shot bundle: a direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layerExperimental Agent Team Profile@deepseek-ai/dsh-experimental-agent-team-profileAgent Teams collaboration, tools, and Web UI in one experimental bundleExperimental Ralph Bundle@deepseek-ai/dsh-experimental-ralph-bundleRun explicitly requested repeated work with fresh agents.Subagent Codex@deepseek-ai/dsh-subagent-codexOne-shot Codex subagent provider over the official app-server protocol

README

dsh-skill-tag

给 DSH 的技能(skill)打标签,然后用一条布尔表达式在「工作区 / 对话」两级 选出真正要下发给模型的技能子集。

装了几十上百个 skill 之后,真正的痛点不是"怎么分类",而是"这次对话只想让模型 看到哪几个"。全部下发会撑爆上下文、也会让路由互相打架;手工一条条开关又太累。 这个插件把这件事变成一条表达式:

pdf + docx            # 只要处理文档的技能
office * plugin       # 只要 office 包带来的、且是插件相关
(aegis + tdd) * !wip  # 两个都要,且排除 wip

它能做什么

能力说明
全局标签建/改名/删除标签,标签之间无层级
标签组模板把表达式存成模板(前端组、只留本机技能…),可复用、可求值
布尔表达式+=OR、*=AND、* 优先、支持括号;改标签名会自动改写所有引用它的表达式
工作区级筛选每个工作区一套条件,作为该工作区新建对话的初始模板;改工作区不影响已存在的对话
对话级筛选每个对话有自己的快照:新建时继承一次,之后冻结;未配置过的对话 = 空条件 = 全部技能;可一键「继承工作区配置」重新拉取
禁用 / 手动添加在标签命中之外额外放行某个技能,或强行屏蔽某个(即使它命中)
技能 ↔ 标签双向编辑「tag 联系 skill」和「skill 联系 tag」两个方向都能改
打开技能所在文件夹任何列出技能的地方都有 📁 按钮,一点即在文件管理器里定位
自带一个 skill安装后你的 agent 会获得 tagging-skills,能照着文档自己给技能打标签

界面预览

全部是真实实例截图(105 个技能 / 48 个标签 / 一个开了筛选的工作区),没有 mock。 图片源文件在 images/(不进 npm 包,README 里用 jsDelivr 绝对地址,GitHub 与 npm 都能显示; 之所以不走 raw.githubusercontent.com,是因为部分网络到该域名的 IPv6 不通,图片会间歇性加载失败)。

图 1 · 入口:会话行「…」→「管理标签」

会话行菜单里的管理标签入口

对话级的配置就挂在会话行的「…」菜单最下面,不用去翻设置页。

图 2 · 入口:命令面板里的 /skill-tag-manage

命令面板中的管理对话标签命令

在输入框敲 / 也能直接开「管理对话标签」——同一个界面,省一次点菜单。

图 3 · 打标签:设置 →「Skill 标签」

Skill 标签管理页:新建标签、按来源分组的标签列表

三个页签分工明确:tag 联系 skill(一个标签包含哪些技能)、skill 联系 tag (一个技能拥有哪些标签)、编辑标签组(写表达式存模板)。 标签按来源分组(来自:aegis 23 个 Skill),每条带 tagId 和「全局删除」。

图 4 · 一个技能看它身上有哪些标签

skill 联系 tag:左列技能、右列标签勾选

左列 技能 (105) 每行标着它有几个标签;右边勾选即时保存,不用点确认。 右列会把该技能已有的标签排在列表最前面(并保持它们原来的相对顺序),所以不用滚动 去列表里找;顶部搜索框过滤之后,"已拥有优先"依然成立。 这是配条件前最常开的一页:先看清楚"这个技能到底被哪些标签命中"。

图 5 · 工作区「管理标签」:左写条件,右就是下发名单

工作区管理标签双列界面

左列是标签条件,下面实时显示解析结果和状态「有条件筛选(严格子集)」; 右列标题是 技能 (54) · 模型可见 48:前一个数是命中表达式的全部技能, 后一个数是其中真正下发给模型的那些。两者不等时标题下方会多出橙色说明, 列出差额的去向——X 个只能 /名字 手动调用(SKILL.md 里写了 disable-model-invocation: true) 和 Y 个宿主没注册(扫描到了但宿主没装),对应行也会带 仅手动 / 未注册 徽标并置灰。 还有第三种状态 暂不可判定:这一趟没拿到完整的宿主视角(比如该会话没在运行, 宿主的 skill registry 只给了全局层),此时"没返回"不等于"没注册",这些技能带灰色虚线徽标、 不置灰,也不计入 模型可见 N。这三种状态的区别与成因见下一节。 每行都可以「查看 tag」「禁用」,📁 直接打开技能所在文件夹。列表顶部的搜索框只过滤显示 (标题会变成 技能 (匹配/总数)),不改动实际下发的技能集合。底部还能「+ 精细化添加技能」 「查看被禁用的技能」。

图 6 · 对话「管理标签(对话)」:可以有自己的一份

对话级管理标签界面与继承来源

同一个工作区下的某个对话想单独收窄,就在这里改:它会记下继承来源与时间 (改动不影响工作区),左下角一键「继承工作区配置」覆盖回来。

技能注册的三种状态:已注册 / 未注册 / 暂不可判定

界面右列标题 技能 (全部) · 模型可见 N 里的 N,只数已被宿主确认注册的技能。 而"未注册"和"暂不可判定"是两件完全不同的事,别把它们读成一句:

状态判据界面表现是否计入 模型可见 N
已注册这一趟宿主快照里返回了这个名字正常显示✅ 计入
未注册宿主视角完整,但没返回这个名字置灰 + 橙色「未注册」❌ 不计入
暂不可判定这次没取到完整的宿主视角灰色虚线徽标,不置灰❌ 不计入

「宿主快照」是什么

插件判断一个技能"模型能不能看到",是去问宿主:"这条技能你注册了吗?" —— 宿主对这个问题的一次完整回答,就是一次宿主快照(宿主 ctx.skills.snapshot() 返回的技能清单)。没取到快照=这一问没有得到一个可信的完整回答, 于是那批技能既不该标"已注册"(没证据),也不该标"未注册"(更没有证据)。

为什么会取不到完整快照

宿主的技能注册表是分层的:global 层 + 按 agent / preset 展开的层。 不带 scope 去问时,宿主只回 global 层。而覆盖用户级技能根 (~/.dsh/skills、~/.agents/skills)的那些 provider 恰好注册在 agent 层里 —— 一旦 scope 传不下去,这些名字就整段不在回答里,"没返回"于是被误当成"没注册"。

这是 0.1.15 修掉的缺陷(issue #1):此前同一份本机文件,在有 live agent 的对话里是 "已注册",在没在跑的对话里就变成"宿主没注册,模型侧根本不存在"——仅仅换一个对话, 结论就翻转。

可能的触发原因,按实际出现频率排序:

  1. 该会话没有在运行——历史对话、已关闭的标签页、或刚切过来还没起 agent。 agents.list() 里找不到它,scope 传不下去,宿主只给 global 层。这时列表顶部会 标注「该会话未运行,以下为全局视角」。
  2. 会话 ID 对不上——浮层传的是 session-<uuid>,宿主 agent 暴露的 id 若是另一个 字符串(宿主侧依次取 session.id / session.header.id / agent.id),同样等于取不到 agent。
  3. 宿主自己报 complete: false——宿主明确说这一趟没收集全(某个 provider 还在加载、 某个根扫描失败)。插件直接采用宿主的结论,整批降级为不可判定。
  4. 快照抛错或并发守卫被占——snapshotSkills() 返回 null,插件退回纯文件系统自扫描; 这时它更没资格断言"宿主没注册"。
  5. 宿主确实没注册——这种情况"暂不可判定"是保守过头了。但只要拿到完整视角, 插件就会给出明确的「未注册」,不会一直挂着。

想看确定结论怎么办

  • 把对话切到正在运行的那个会话再看——它能拿到完整视角,用户级技能会恢复成"已注册"。
  • 设置页那种不带 conversationId 的口径本身就是全局视角,出现"暂不可判定"是预期行为。
  • 视角不完整时,POST /skill-tag/api/skills/purge-stale 会直接返回 409 INCOMPLETE_VIEW 而拒绝执行——因为它要靠"未注册"这个结论去删关联记录,视角不可信时删了就是误删。

安装

这是一个 DSH(DeepSeek Harness)插件,装进 profile 后随宿主启动加载。

一条命令

dsh plugin --profile <your-profile> add dsh-skill-tag

也可以不走 npm,直接从 GitHub 装:

dsh plugin --profile <your-profile> add github:aawef/dsh-skill-manager-tags

dsh plugin 本质是把参数转发给 profile 目录里的 pnpm,它不会替你启用插件 —— 装完还要在 profile 的 package.json 的 dsh.profile.bundles 数组里加上 "dsh-skill-tag":

{
  "dsh": {
    "profile": {
      "bundles": [ "...", "dsh-skill-tag" ]
    }
  }
}

重启 DSH 后生效(设置页会出现「Skill 标签」一项)。

官方 DSH 桌面端的 desktop profile 归应用独占,命令行改不动它 —— 桌面端请在 Settings → Plugins 或插件市场里安装。

依赖 Node ≥ 20;需要 DSH 0.2.0-rc.1 及以上(见 engines.dsh)。

宿主的 @deepseek-ai/cordis 声明为 peerDependency,由 DSH 本体提供,不要自己安装。

存储域 spec 由本插件 src/storage/domain-spec.js 本地构造(等价于宿主的 defineDomain / domainTable),运行时通过 ctx.storageDomain.open() 交给宿主打开,不 import 任何宿主内核包。 这样 profile 里不会出现与宿主同名的第二份模块副本:早期版本曾 import @deepseek-ai/dsh-storage-domain,宿主升级(如 0.2.1-rc.2 → rc.3)时那份旧副本会被判定 不兼容并拒绝加载,导致存储域整体不启动、会话与工作区列表全空(详见 CHANGELOG 2026-10-24)。

本地开发式安装

运行时加载的是 profile 里 node_modules/<插件名> 那一份,不是你的源码目录。两种接法:

  • junction(推荐):让 ~/.dsh/profiles/web/my-plugins/<插件名> 指向你的源码目录, pnpm 会把它硬链接进 node_modules —— 三个路径是同一个 inode、同一份文件, 改完内容立刻生效,不需要复制;只有新增/删除文件才要在 profile 里重跑一次 pnpm install。
  • 物理拷贝:如果当初是手动 cp 进去的,那就是两份独立文件,改完必须再 cp 一次并校验哈希:
S=/path/to/your/dsh-skill-tag
D=~/.dsh/profiles/<profile>/node_modules/dsh-skill-tag
cp "$S/lib/client.js" "$D/lib/client.js"
sha256sum "$S/lib/client.js" "$D/lib/client.js" | cut -c1-16

生效规则(很重要,搞混了会以为"改了没用"):

文件改完之后
lib/client.js刷新页面即可(宿主 no-cache 加载)
lib/index.js、src/**、cordis.patch.yml重启桌面进程

怎么用

1. 打标签

设置 →「Skill 标签」,三个按钮:

  • tag 联系 skill —— 选一个标签,勾它包含哪些技能
  • skill 联系 tag —— 选一个技能,勾它拥有哪些标签
  • 编辑标签组 —— 写表达式、存成模板

界面长什么样见上方「图 3」「图 4」。

也可以让 agent 自己做:安装后 tagging-skills 这个 skill 会出现在技能列表里, 里面是完整的推导法 + API 调用纪律。不要照抄文档里的标签名(aegis、office-skills 只是某一台机器上的实例),按自己机器的技能清单推导。

或者直接跑一键脚本(先 dry-run):

node tools/auto-tag.mjs http://127.0.0.1:<PORT>                  # 只看计划,不落地
node tools/auto-tag.mjs http://127.0.0.1:<PORT> --apply          # 落地来源轴
node tools/auto-tag.mjs ... --domain ./my-domain-map.json --apply # 连领域轴一起

脚本幂等,重跑应输出 tagsCreated: 0。

2. 给工作区/对话配条件

  • 工作区:侧栏工作区行的「…」菜单 →「管理标签」
  • 对话:会话行的「…」菜单(图 1),或聊天框左下角「+」→「管理对话标签」(图 2)

左列写标签条件表达式,右列是命中结果(可手动添加 / 禁用 / 恢复)。改完立刻生效: 该对话下发给模型的技能列表就是右列里模型可见的那一份(标题的第二段数字)。 两张双列界面见「图 5」「图 6」。

3. 表达式语法

语法含义
A + BOR
A * BAND
(A + B) * C括号;* 优先级高于 +
"my tag"标签名含空格或 + * ( ) 时加引号(所以起名请用 kebab-case)
空串无条件 = 全部技能(合法的"不筛选")

表达式会被编译成 DNF({orTags, andGroups})再交给匹配引擎。 标签改名时,所有引用它的表达式会被按 token 原位改写,不会失效。

4. 路由型技能(router)别被拆散

有些技能的职责就是「帮你挑别的技能」——description 里出现 Use when the user asks… Router over the skills… 这类措辞,还常带有 skill-filesystem 之类的下挂资源。

插件只按名字屏蔽,从不改任何 SKILL.md 的内容,所以路由器自己的提示词一个字都不会变。 会坏的是依赖关系:路由器可见、而它指向的技能被屏蔽时,模型照着提示去加载只会拿到 skill "X" is not available for model invocation —— 这一轮路由白跑,还可能让模型反复试。

所以配条件时请把路由器和它的簇当成一个整体(用簇标签一次圈住,别按领域标签零散地挑):

你想要的表达式
整个簇(路由器 + 7 个 office 技能)routes:office
只要路由器本身,不要成员router * routes:office
只要成员,不要路由器(自己挑,不需要分发提示)routes:office * !router

插件运行时不认识"谁是路由器"(router 只是标签约定),这一层耦合靠表达式自己保证完整。 选完可以用 POST /skill-tag/api/workspaces/skills 回读 candidates,确认路由器的簇成员没被拆散。

HTTP API

挂在 /skill-tag/api 前缀下。所有路由同时接受 GET 和 POST (q(k) = body[k] ?? url.searchParams.get(k)),所以浏览器直接敲 URL 也能调试。 响应一律是 JSON;出错是 {ok:false, error, code}。

路由方法主要参数说明
/stateGET—全量快照:tags / skillLinks / templates / workspaces / conversations
/skillsGETcwd可打标签的技能清单(带 path)。带 cwd 才能扫到项目级 .dsh/skills
/skills/tagsGET/POSTskillName, tagIds读/写一个技能拥有的标签。写是整体覆盖,见下方警告
/skills/revealPOSTskillName 或 path拉起文件管理器定位该技能
/tagsPOSTtagId, name建标签(同名会报 already exists)
/tags/renamePOSTtagId, name改名并同步改写所有表达式
/tags/linkPOSTskillId, tagIds关联
/tags/unlinkPOSTskillId解除关联
/tags/deletePOSTtagId全局删除,返回 affectedWorkspaces
/templates/validateGET/POSTexpression校验 → {ok, errors, normalized, dnf, config}
/templates/evaluateGET/POSTtemplateId求值(要 templateId,不是 expression)
/templates/expandGET/POSTtemplateId展开
/templates /templates/update /templates/deletePOST—模板 CRUD
/workspaces/expressionPOSTworkspaceId,

警告:/skills/tags 的写是整体覆盖。写之前必须先 GET 读回旧 tagIds 再合并,否则会清掉已有标签。这是最容易踩的坑。

目录结构

lib/index.js         宿主侧入口(ESM):注册 filter provider / storage domain / HTTP API / 自带 skill
lib/client.js        浏览器侧界面(__ModuleLoader__ 工厂,无构建步骤)
src/domain/
  expr.js            表达式引擎:tokenize / parse / toDnf / dnfToConfig / validateExpression
  match.js           匹配:matches / candidatesFor
  origin.js          来源轴推导(从 path 推出这个技能是哪个包带来的)
  conv.js            对话四区状态(加载/豁免/未加载/屏蔽)
  workspace.js       工作区视图
src/api.js           HTTP 路由
src/service.js       服务层(CRUD + 改名同步 + 迁移)
src/storage/         storage domain schema + repository
src/reveal.js        拉起系统文件管理器
skills/tagging-skills/  随插件注册给宿主的一个 skill
tools/auto-tag.mjs      一键打标签脚本(dry-run 默认)
tests/               node:test,194 项
docs/                设计文档(见下)
images/              README 用的界面截图(不进 npm 包)

文档

文件内容
docs/给技能打标签.md打标签的完整手册:三轴设计、API 调用序列、错误对照表、交付检查清单
docs/标签组配置.md表达式编辑页的设计与验收
docs/管理标签.md工作区双列配置
docs/对话管理标签.md对话级配置与继承
docs/技能发现与宿主对齐.md为什么"插件看到的技能数"曾经少于宿主,以及怎么对齐
docs/deny-shadow-覆盖率.md为什么包带来的技能会静默漏网(path 为 null 的那批),以及宿主对影子候选的强校验、层/rank 语义
docs/修复指南.md「打了标签技能却没出现」的排查手册:三层判定树、transcript 逐帧解压取证、预设关闭默认技能根(A 层)、已知假阳性、升级回归清单
docs/skill联系tag与打开文件夹.md两个方向编辑 + 📁 按钮
CHANGELOG.md按日期倒序的完整开发记录

开发

npm install
npm test          # 194 项

测试分两类,都在 tests/ 下:

  • *.test.mjs —— 单元/领域测试,被 node --test 自动收集
  • __*.mjs —— 手工脚本:HTTP 全链路冒烟(__api-smoke)、用迷你 React 运行时 执行真实组件并断言渲染结构(__page-render 等)

两个 glob 都要,npm test 已经配好;只跑 tests/**/*.test.mjs 会漏掉 46 个用例。

界面预览可以在没有浏览器扩展时生成静态 HTML:

node docs/preview/render-preview.mjs     # → docs/preview/标签组配置-preview.html

(产物已在 .gitignore 里排除。)

改插件时的几条硬约束

这几条都是踩过的坑,源码注释里也写了:

  • 宿主服务是声明式注入的:没写进 export const inject 的服务,ctx.<service> 读属性会直接抛 cannot get property "x" without inject。而且 ctx.x ?? ctx.get("x") 这种兜底照样抛 —— 左侧求值时就炸了。只能 ctx.get() + try/catch。
  • 注册给宿主的回调里抛异常 = host fatal(整个桌面进程挂掉)。 domain/changed 监听器、provider 的 list/get、apply() 同步栈,全部 self-contain。
  • 新增 HTTP 路由必须重启桌面进程,否则新路由 404 —— 这个 404 极易被误判成 "路由名写错了"。
  • ctx.effect(() => fn()) 的 thunk 立刻执行,它的返回值才是 dispose。 写成 ctx.effect(() => domain.close()) 会在装配阶段就把 domain 关掉。

许可证

MIT — 见 LICENSE。

expression
改工作区条件
/workspaces/skillsGETworkspaceId读该工作区的候选项(candidates)
/workspaces/skills/add remove disable enablePOSTworkspaceId, skillName手动添加/移除/禁用/恢复
/conversations/configGET/POSTconversationId对话双列配置的读/写
/conversations/inheritPOSTconversationId整体覆盖式继承所属工作区配置
/conversations/candidatesGETconversationId该对话实际生效的技能列表