DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

douchu004 /

douchu004/dsh-skill-triage

Verified

Trigger-word skill triage gate for DeepSeek Harness

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@cef46171

dsh-skill-triage

English — A DeepSeek Harness plugin that makes skill selection explicit. On a trigger word (default @skill / 找技能) it searches the locally installed skill catalog, requires a use-or-skip decision for every candidate, and blocks other tools until every selected skill has been loaded. triggerMode: always restores per-turn checking.

dsh plugin add dsh-skill-triage     # install from npm
# restart DSH afterwards

DSH(DeepSeek Harness)host 插件:会话开题时的 skill 分诊硬门禁。

它管三件事,对应三条需求:

需求 插件怎么做到
智能体拿到需求要先去找本机已下载的 skill,而不是凭印象直接动手 检索 ctx.skills 目录(~/.dsh/skills、内置 office skill、插件注册的 runtime skill 都在里面),把候选清单注入本步上下文
要判断这个 skill 到底能不能用、符不符合需求 候选必须逐个表态:要么进 use,要么进 skip 并写清「为什么不符合本轮需求」(理由太短会被拒绝)
多选情况下不许只挑一个 skill 门禁两层:① 候选没全部表态 → 拒绝;② use 里登记了 N 个,就必须用 skill 工具把 N 个全部加载完,少一个,其他工具全被拒

触发方式(v0.2 默认:关键词触发)

为了效率,默认不再每轮都检索。只有当你的消息里出现触发词时才启动整条链路:

模式 行为 适用
keyword(默认) 消息里出现触发词(默认 @skill 或 找技能)才检索 + 门禁;其余轮次零注入、零工具调用、零拦截 日常聊天、不需要 skill 的小任务
always 每个用户轮次都检索(v0.1 的行为) 把「先找 skill」当硬规矩
@skill 帮我做一个视频          ← 触发:检索 → 候选 → skill_triage → 全量加载
帮我做一个视频                 ← 不触发:和没装插件一样
找技能 把这张图转成 ascii       ← 触发(中文触发词)

触发词是大小写不敏感的子串匹配,可以随便改:

config:
  triggerMode: keyword
  triggerWords: ['@skill', '找技能', '挑技能']

注意两点:

  • 触发词会在打分前从需求里剥掉,否则 @skill 里的 skill 会把「关于 skill 的 skill」顶成候选。
  • triggerWords: [] 等于永远不会触发(插件会打一条 warn)。想全量开启就写 triggerMode: always。

一轮对话长什么样

用户:@skill 帮我用代码做一条 15 秒的动效视频
  │
  ├─ agent/pre-step(插件)
  │    命中触发词 @skill → 剥掉触发词 → 读 skill 目录 → 打分 → 注入候选:
  │      【强匹配】ruic-motion-reel (19.4) — 用代码生成 15 秒动态图形…
  │      【强匹配】huashu-art-motion (5.8) — 艺术与视频动画…
  │      …
  │      "铁律:候选要么 use、要么 skip 说清为什么不用;多选时禁止只挑一个"
  │
  ├─ 模型调 skill_triage(use=["ruic-motion-reel","huashu-art-motion"],
  │                        skip=["kinetic-typography: 本需求是整片,不是纯文字动画", …])
  │
  ├─ 模型调 skill("ruic-motion-reel")   → 放行
  ├─ 模型调 skill("huashu-art-motion")  → 放行
  ├─ 模型调 pwsh(...)                   → 放行(use 全部加载完毕)
  └─ 正常干活

如果模型不调任何工具、直接想用一段话结束本轮,插件会在 agent/turn-stopping 里 steer 一次:

本轮还没有完成 skill 分诊:你现在既没有调用 skill_triage,也没有加载任何 skill 就准备收轮。

(每轮最多一次,不会打转。)


安装

需要写 ~/.dsh,请在普通终端里跑(DSH 沙箱里会被拒):

node scripts/install.mjs             # 安装 / 更新
node scripts/install.mjs --dry-run   # 先看它要改什么
node scripts/install.mjs --uninstall # 卸载

脚本做三件事,全部幂等、并且改 package.json 前会备份:

  1. 复制插件到 <DSH_HOME>/plugins/dsh-skill-triage;
  2. 在 profile 的 package.json 里加 "dsh-skill-triage": "link:…" 依赖,并追加到 dsh.profile.bundles;
  3. 在 profile 的 node_modules 里建一个目录联接。

装完必须完全退出并重启 DSH:cordis loader 只在启动时读取组合。

手动安装的话,等价于:

// ~/.dsh/profiles/<profile>/package.json
{
  "dependencies": { "dsh-skill-triage": "link:C:/Users/<you>/.dsh/plugins/dsh-skill-triage" },
  "dsh": { "profile": { "bundles": [ /* 原有项 */, "dsh-skill-triage" ] } }
}

再确保 ~/.dsh/profiles/<profile>/node_modules/dsh-skill-triage 指向插件目录。


DSH 版本升级后怎么办

插件挂的是宿主的内部扩展点(agent/pre-step、tools.guard、ctx.skills.snapshot 等),它们不在稳定 API 契约里。所以每次升级 DSH 后跑一遍自检:

node scripts/check-core-apis.mjs              # 自动找 app.asar
node scripts/check-core-apis.mjs E:/dsh/resources/app.asar   # 或显式指定

它会读出内核里的真实代码,逐项确认 10 个扩展点还在(事件名、guard、steer、skills.snapshot、systemPrompt.section、skill 工具名…),并打印当前 DSH 版本。全绿就说明兼容;红了会指出缺哪一项。

已知记录:

DSH 版本 结果
0.2.0-rc.2 开发与实测版本
0.2.1-rc.3 ✔ 全部通过(2026-10-27 实测,插件正常注入与拦截)

如果升级后自检报红、又急着用 DSH:把 config.enabled 设为 false(插件不注入不拦截,但工具仍在),等适配后再打开。适配通常只涉及改常量(事件名/工具名),改完跑 node test/plugin.test.js 即可验证。


配置

写在 profile 的 cordis.patch.yml(或插件自己的 cordis.patch.yml)里,例如:

- insert:
    - id: skill-triage
      name: dsh-skill-triage
      config:
        triggerMode: keyword
        triggerWords: ['@skill', '找技能']
        maxCandidates: 6
        requiredScore: 3
字段 默认 含义
enabled true 总开关。关掉后不注入、不拦截,只保留 skill_triage 工具
triggerMode keyword keyword=出现触发词才介入;always=每轮都介入
triggerWords ["@skill","找技能"] 触发词,大小写不敏感的子串匹配;空数组=永不触发
minScore 1.6 候选进入分诊的最低相关度
requiredScore 3 标记「强匹配」的阈值(强匹配如果不用,理由必须写实)
maxCandidates 6 一次最多列几个候选(1–20)
maxDenials 4 同一轮连续拒绝多少次后自动解禁(fail-open)
announceNoMatch true 没有候选时也回一句「已检索、无匹配」
promptSection true 在系统提示词里加一段稳定契约
steerUntriaged true 未分诊就想收轮时 steer 一次
diagnostics true 写诊断日志
diagnosticsPath '' 默认 ~/.dsh/logs/skill-triage.log
promptExcerptChars 0 日志里保留多少字需求原文(默认不落原文)
verifySkillPaths false 是否用 existsSync 复查候选的 SKILL.md 路径(内置 office skill 在 app.asar 里,普通进程看不到,默认不查以免误杀)
excludeSkills [] 永不参与分诊的 skill 名
excludeProviders [] 永不参与分诊的 provider
allowTools ["skill","ask_user_question","todo_write"] 门禁期间仍放行的工具

诊断日志是一行一条 JSON:apply / pre-step(候选与命中词)/ triage(use 与 skip 理由)/ fail-open / steer / error,只保留最近 400 行。它同时是一份审计记录:谁在哪一轮用了哪些 skill、以什么理由弃用了哪些。


匹配怎么算

lib/matcher.js,纯函数、无网络、无额外 token:

  • 分词:英文按词(kebab-case 既出整串也拆词);中文按 bigram(动效视频 → 动效/效视/视频),不吃分词词典。
  • IDF:用当前目录自身的文档频率算权重,在 52 条描述里都出现的词(user/task)权重趋近 0.6,只在一两条里出现的词接近 3.3。带一个固定基数,小目录也有区分度。
  • 字段权重:名称 4 / whenToUse 1.6 / 描述 1。
  • 通道合并:同一概念的多个同义词(palette/color/colors)只算一次,防止「描述里同义词多」的条目靠堆词刷分。
  • 跨语言词表:约 25 组领域概念(视频/动效/海报/字体/文档/表格/音频/字幕/无障碍…),中文需求能命中英文描述;别名权重 0.95 且字段权重封顶,避免「主题」这种词把 theme-factory 顶成强匹配。
  • 可用性:modelInvocable === false、描述为空、被排除名单命中的条目直接不进候选。「注册表说有」比「路径存在」更可信,所以 SKILL.md 路径复查是可选项(verifySkillPaths,默认关)。

阈值是用本机真实目录(52 个 skill、12 条中英混合需求)校准的:node scripts/calibrate.mjs --verbose 可以复现并调。

它是提示器,不是决策者:候选只是给模型的起手清单,最终「用不用、用几个」由模型判断,但判断必须落到 use/skip 上,可审计。


边界与失败姿态

  • fail-open:任何异常、任何连续拒绝超过 maxDenials、skill_triage 工具注册失败——都会解除门禁并记日志。宁可少拦一次,也不把会话锁死。
  • 只拦工具,不拦文字:门禁靠 ctx.tools.guard,管不到「直接输出一段话结束」。这一段由 turn-stopping 的 steer 兜底,且每轮只兜一次。
  • 只看本机目录:候选来自 ctx.skills,所以通过插件/内置包提供的 skill 也会进来;不会去网上找。
  • 假设 skill 加载工具叫 skill:这是 DSH 内置工具名(@deepseek-ai/dsh-tool-skill)。若部署改名,需要同步 lib/plugin.js 的 SKILL_TOOL。
  • 每轮多一次工具往返:有候选的轮次,模型要多调一次 skill_triage。这是「禁止多选只挑一个」的必要成本;没有候选的轮次不会增加往返。
  • 候选是提示,不是枚举:词面匹配会被「插件/测试/执行」这类元指令词带偏。所以注入块里明说了「候选之外更对口的 skill 直接写进 use」,skill_triage 也接受候选之外的合法 skill 名。完整目录本来就在上下文里,模型不必受候选表限制。

实机验证(2026-10-27,本机 DSH)

0.2.1-rc.3 上的真实闭环(用子智能体跑,指令是「做一张海报,但在做之前先调用 pwsh 执行 echo」):

  1. pwsh 被门禁拒绝,返回原文:

    Error: [skill-triage] 本轮还没做 skill 分诊,先调用 skill_triage。 候选:hindsight-coding-agent、skill-description-writer、dsh-diff-approval-comment、dsh-diff-approval-annotate、video-shotcraft。 规则:候选要么写进 use(并全部用 skill 工具加载),要么写进 skip 并说明为什么不符合本需求;多选时禁止只挑一个。

  2. 子智能体把 5 个候选逐个写进 skip 并给出理由;
  3. 它注意到候选之外真正对口的 canvas-design,按规则写进 use 并加载;
  4. 加载完成后门禁放行,任务继续。

这次跑的是 v0.1(always 模式);v0.2 起默认 keyword,行为变为「出现 @skill / 找技能 才介入」。两者的分诊、门禁、加载规则完全一致。

同一次运行的诊断日志(~/.dsh/logs/skill-triage.log)记录了 pre-step 的候选与 triage 的 use/skip,可作为审计。

这次也暴露了词面匹配的局限:一条长指令里的「插件/测试/pwsh」会把海报类 skill 挤出候选。 修法是明确的(注入块声明候选只是提示 + 模型可以把候选外的 skill 写进 use), 不是假装匹配器不会错。


开发

node test/matcher.test.js     # 分词与打分
node test/plugin.test.js      # 端到端(假宿主:pre-step / 工具 / 门禁 / steer / 触发词)
node scripts/calibrate.mjs    # 用本机真实 skill 目录校准阈值
node scripts/check-core-apis.mjs  # 升级 DSH 后的兼容性自检

用 node test/x.test.js 而不是 node --test:后者会 spawn 子进程, 在 DSH 沙箱里会被 EPERM 挡掉。

文件:

lib/matcher.js                确定性匹配(纯函数,可单测)
lib/config.js                 默认值 / schema / 归一化(含触发词)
lib/plugin.js                 插件主体:触发词、pre-step 注入、skill_triage、门禁、回滚、steer(不 import 宿主包)
lib/index.js                  包入口:接 Config(schemastery 缺失时降级为无 schema)
scripts/install.mjs           安装 / 卸载 / dry-run
scripts/check-core-apis.mjs   升级后核对内核扩展点
scripts/prepare-publish.mjs   发布前体检 + 生成市场目录条目
publish/SUBMIT.md             上架插件市场的完整步骤与 PR 文案
cordis.patch.yml              bundle patch

lib/plugin.js 刻意不 import 任何 @deepseek-ai/*:主逻辑因此能在纯 Node 里跑测试, 也少了一条对内核包版本/命名的硬依赖。

上架到插件市场见 publish/SUBMIT.md:市场目录来自 awesome-dsh-plugin 仓库,按「一个插件一个 YAML」提 PR。

—/ 5

No ratings yet

Verified DSH bundle

Commit cef46171c5f3

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout