dsh-v-skill-links · V 技能映射
简体中文 | English
DeepSeek Harness Web 的 「/」菜单映射技能 插件。
在插件配置里声明任意多个技能目录后,每个目录下的一级子目录只要含 SKILL.md(大小写不敏感,Skill.md/skill.md 均可),该目录就成为一条映射技能:在会话输入框按 / 唤出的菜单里,「命令」「技能」之下新增一个 「映射技能」 分组列出它们;选中后与原生技能同形地落入 /名字 文本,发送时宿主端读取该技能的 SKILL.md 全文,以与原生技能一致的 <skill_content> 块注入本步 —— 「使用」即「引用该 skill.md」。
纯增量插件:不替换、不禁用任何官方插件;映射技能不进模型的 <available_skills> 目录(不占系统提示词),仅由用户显式引用。
功能
- 多目录:
config.directories接受任意多个目录;绝对路径原样使用,~展开为用户主目录,相对路径按 dsh 启动目录解析;重复路径去重。 - 扫描规则:只看配置目录的一级子目录;子目录内存在
SKILL.md即收录。.开头的隐藏目录跳过。技能名默认取子目录名(空白//归一为-);SKILL.mdfrontmatter 里的name:(不含空白时)与description:优先;缺描述时取正文第一个非空行(markdown 标题去#)。两个目录出现重名技能时按配置顺序先到先得。 - 「映射技能」分组(置顶):客户端注册独立的
/触发源,分组标题即「映射技能」,order: -1排在全部官方分组(命令=默认 0、技能=2)之前;子串模糊匹配(大小写不敏感,/cd命中abcd,前缀命中优先排序);配置了多个有效目录时,候选描述会追加· 根目录名便于区分来源。 - 「自定义指令」分组(followup 型):
order: -0.5紧跟映射技能、命令之前。在设置卡片里维护指令列表(名称 + Prompt),选中菜单项立即把 Prompt 作为用户消息发送进当前会话(宿主ctx.agents.get(sessionId)→agent.followup(...),消息打{ kind: "user" }源,与官方/plan同口径)——Prompt 里可写/映射技能名令牌,发送时会连带注入对应 SKILL.md 全文;成功/失败有 toast 反馈。同样子串模糊匹配。不注册进宿主命令注册表,因此不与官方「命令」分组重复。 - 快捷按钮(输入框下方网格):官方
conversation.composer.dock槽位(输入卡片下方,与官方 stats 行并肩、紧贴卡片)渲染按钮网格。槽位契约只给宽度列,「与输入区同宽」由网格自我约束:max-width: var(--dsh-chat-content-width)+ 居中,左缘与输入文本左缘重合(官方 stats 行同款宽度策略);CSS Grid 行优先先横排,列auto-fill minmax(96px, max-content)、单钮max-width: 220px——按钮随内容收缩、自左聚拢成功能按键观感(不拉伸填满整行),高度无上限。新增会话首屏(空白会话 hero)经官方conversation.input.dock槽位(idv-quick-buttons-hero)以 CSSorder渲染同一网格于 hero 输入卡片正下方(非 blank 自动让位给会话态的 composer.dock 条目);无会话首屏不渲染。按钮由设置卡片维护(名称 + Prompt + 自动发送):点击 =InputActions.setDraft把 Prompt 追加进输入框(草稿非空时换行拼接,一次机器事务可撤销);勾选自动发送 = 追加后InputActions.submit()(等同按下回车:发送整个输入区、/令牌照常裁决、运行中会话按回车行为设置 steer/排队)。名字超宽截断(ellipsis)+ 悬停title显示完整 Prompt;空列表零布局;提交期间(phase 非 plain)整体禁用。按钮底子是官方primitives.Button(outline/sm),颜色全部走--dsw-alias-*token(明暗主题自动跟随),并暴露.v-qb-grid/.v-qb-btn/data-v-button="<名称>"稳定 CSS 钩子供用户美化。 - 引用即注入:选中候选落入
/名字,发送后宿主agent/pre-step监听器(与官方dsh-time-context同一模式,prepend跑在链最外层)在直接用户输入里寻找空白边界的/名字令牌,命中即读取SKILL.md,渲染为<skill_content name="…" source="…\SKILL.md">+<skill_resources>(基目录提示)+<skill_instructions>(正文),作为 user 角色的 instructions 注入追加进本步 —— 一次步内同一技能只注入一次;正文超 20 万字符截断并提示读取原文件。 - 热与新鲜:目录列表 3 秒 TTL 缓存(客户端 5 秒),注入时实时读文件——改完 SKILL.md 立刻生效;profile 补丁层热重载(改配置不用重装)。
- 可视化设置页:宿主注册设置命名空间
v-skill-links,并在设置页左栏注册第一层「技能管理」节(官方settings.section槽,idv-skills),节内三个标签页(官方插件页同构的 tablist):技能映射(技能目录 textarea)、自定义指令(列表 CRUD)、快捷功能(快捷按钮 CRUD,含自动发送勾选),暂存-保存模型、覆盖标记、段级重置;保存写入用户设置文档(settings.yaml),与补丁层配置叠加(用户层优先),保存即热生效;旧「插件配置 → 映射技能」卡片已移除,避免重复入口。 - 调试接口:
GET /api/v-skill-links/list返回{ skills, directories }(directories 逐条报告 ok/error/count);GET /api/v-skill-links/skill?name=名字返回单条技能的原文与 frontmatter。接口不接受任意路径输入(只能按名字查当前目录里的技能),无路径穿越面。
安装
方式一 · GitHub 地址直装(推荐)——本插件手写零构建、无任何安装期脚本,git 安装无需构建授权:
dsh plugin --profile web add "github:victor10035445/dsh-v-skill-links"
如需锁定版本(后续推送不会悄悄改变实际运行的代码):
dsh plugin --profile web add "github:victor10035445/dsh-v-skill-links#<commit-sha>"
方式二 · 本地 clone + link 直连(免打包,改动后重启 dsh web 生效,适合开发调试):
git clone https://github.com/victor10035445/dsh-v-skill-links.git
dsh plugin --profile web add "link:<克隆路径>"
方式三 · tgz 打包安装:
npm pack # 产出 dsh-v-skill-links-0.3.0.tgz
dsh plugin --profile web add "<tgz 的绝对路径>"
装完重启 dsh web,刷新页面生效。
配置
编辑 $DSH_HOME\profiles\web\cordis.patch.yml(用户的补丁层,追加条目):
- id: dsh-v-skill-links
config:
directories:
- D:/skills/my-skills # 绝对路径
- ~/my-skills # ~ 展开为用户主目录
- ./skills # 相对 dsh 启动目录
buttons: # 快捷按钮(可选;组合层预置,用户层设置卡片可覆盖)
- name: 新建
prompt: 使用 xxx 技能做 xxx 事
autoSend: true # 填入后立即按回车语义发送整个输入区
- name: 审查
prompt: 请使用 /code-review 审查当前改动
保存后补丁层热重载;若未生效则重启 dsh web。配置合法但目录不存在不算致命——菜单空组自动隐藏,/api/v-skill-links/list 的 directories 里可见具体错误。
可视化设置页(推荐日常入口)
侧边栏齿轮打开 设置 → 技能管理(左栏第一层),页内三个标签页:
- 技能映射:技能目录 textarea 每行一个目录;「保存」写入用户设置文档(
$DSH_HOME\settings.yaml的v-skill-links节),立即热生效,无需重启; - 自定义指令:点「新增」添加一行指令,编辑名称(显示在
/菜单「自定义指令」分组,小写a-z 0-9 _ -)与 Prompt(选中后发送进会话的内容);行上 ✕ 删除;保存后立即出现在/菜单; - 快捷功能:点「新增」添加一行按钮,编辑名称(按钮上显示,可中文,仅要求非空且不重名)、Prompt(点击后追加进输入框的内容)与自动发送勾选框;保存后立即出现在会话输入框下方网格;顺序调整:行首把手(⠿)拖拽——拖拽过程在候选落点显示指示线、松手落下确定新位置,或用每行的「上移 / 下移」按钮逐位交换(触屏与键盘的平级通道);两种通道都只改本地草稿,随「保存」生效;
- 三个标签页共享同一份暂存草稿:任一页的修改计入整体脏标记,任一页底部「保存」一次写全;各页的「重置为组合层 / 重置指令 / 重置按钮」仅清除自己段的用户层覆盖(回退到补丁文件配置);
- 非 loopback 浏览器或设置文档只读时各页自动禁用写入(官方设置域的行为)。
SKILL.md 示例(~/my-skills/code-review/SKILL.md)
---
name: code-review
description: 按团队规范审查当前改动并输出分级意见
---
# Code Review
1. 先 `git diff` 看改动……
不写 frontmatter 也能用:技能名取目录名 code-review,描述取正文第一行 # Code Review(去 #)。
使用
- 会话输入框输入
/→ 菜单出现 命令 / 技能 / 映射技能(/ 子智能体) 分组; - 在「映射技能」里选中一条(或手打
/名字); - 补一句话(可选)后发送 —— 模型在本步收到你的消息原文 + 该技能
SKILL.md的<skill_content>注入块,并按其指示行动。
自定义指令:在设置卡片里维护指令后,/ 菜单的「自定义指令」分组(映射技能之下、命令之前)选中一条 → 立即把该指令的 Prompt 作为用户消息发送进当前会话(toast 确认;Prompt 里写 /映射技能名 令牌会连带注入对应 SKILL.md 全文)。适合"固定套路一键触发"。
快捷按钮:在设置卡片里维护按钮后,会话输入框下方出现按钮网格。点击按钮 → Prompt 追加进输入框(草稿非空时换行拼接,Ctrl/Cmd+Z 可整体撤销);勾选了自动发送的按钮在追加后立即等同回车发送整个输入区——发送前你仍可以补改草稿。Prompt 里写 /映射技能名 或 /原生技能名 令牌,发送时照常注入。按钮的展示顺序 = 设置里 buttons 数组的顺序:在设置「快捷功能」标签页用行首把手拖拽或「上移 / 下移」按钮调整并保存后,输入框下方网格与新增会话首屏 hero 网格即时按新顺序重排(dock 只显示、纯跟随,不支持在 dock 内拖拽;新增按钮一律排在末尾)。
快捷按钮 vs 自定义指令
| 快捷按钮 | 自定义指令 | |
|---|---|---|
| 入口 | 输入框下方网格,常驻可见 | / 菜单「自定义指令」分组 |
| 名称 | 显示名,可中文(仅非空+不重名) | 令牌名,小写 a-z 0-9 _ - |
| 点击行为 | 追加进输入框(可继续编辑草稿) | 立即发送(不经输入框) |
| 自动发送 | 可选勾选(填入后等同回车) | 天然即发送 |
| 发送通道 | 客户端 InputActions(回车管线,与手打同口径) |
宿主 followup(要求会话有活跃代理) |
| 适合 | 起草模板 / 引用技能后再补话 | 固定套路一键直发 |
架构
| 端 | 内容 |
|---|---|
宿主 lib/index.js |
配置归一化(~/相对路径、指令名称校验/去重、按钮名称/去重/autoSend 布尔);设置命名空间 v-skill-links 注册(手写 schema 兼容 dsh-settings 的可调用/toJSON/walker 契约,免 schemastery 依赖;补丁层配置为组合 base,settings.yaml 用户层覆盖,scope.watch 热切换);目录扫描 + TTL 缓存;/api/v-skill-links/list(含 commands)、/run(followup 执行)、/skill;agent/pre-step(prepend)注入 <skill_content>;消息构造与 dsh-time-context 同模式(deep-freeze)。 |
客户端 lib/client.js |
① / 触发源「映射技能」(order -1 置顶):fetch /list(TTL + 旧数据兜底)、startsWith(query) 过滤、warm/lexicon/subscribeLexicon、onPick 落 /name 字面文本。② / 触发源「自定义指令」(order -0.5):候选来自 /list 的 commands,onPick 立即 POST /run(宿主 followup 进会话)+ toast 反馈;不提供 lexicon(手打令牌不产生引用语义)。③ 快捷按钮网格双条目:conversation.composer.dock(id v-quick-buttons、order -1 紧贴输入卡片)+ conversation.input.dock hero 条目(id v-quick-buttons-hero、order 0;新增会话首屏 blank 态渲染同一网格于 hero 卡片下方,非 blank 返回 null 与前者互斥):createButtonsStore 投影 settingsScope 的 buttons 节(共享作用域,保存即热更新),QuickButtonsGrid 用官方 primitives.Button(outline/sm + 自有 class 覆盖方形)渲染网格,点击 InputActions.setDraft 追加 / submit() 等同回车,phase 非 plain 禁用。④ 设置左栏第一层「技能管理」节(settings.section,id v-skills)+ 三个标签页(settings.v-skills.tab:技能映射/自定义指令/快捷功能),共享同一 settingsScope.bind({namespace, decode}) 控制器,目录 textarea 与指令/按钮列表(增删改、按钮排序 moveButton)暂存-保存(set/unset),覆盖标记与段级重置。 |
声明 package.json |
dsh.bundle.patch 挂 loader 条目;dsh.client 让 client-modules 把 lib/client.js 编入 /plugins 启动图(零 npm 依赖,无需构建步骤)。 |
与原生技能的关系
| 原生「技能」 | 映射技能 | |
|---|---|---|
| 来源 | dsh-skill-filesystem 扫描的项目/用户技能根 |
本插件配置目录的一级子目录 |
| 模型目录 | 进 <available_skills>,模型可自主加载 |
不进目录,仅用户显式 /名字 引用 |
| SKILL.md 要求 | frontmatter 校验较严 | 任意 markdown,frontmatter 可省 |
已知边界
- 与命令重名:映射技能名与宿主命令同名(如
plan)时,命令侧裁决优先,该名字只会触发命令——给技能起个不像命令的名字即可。 - 自定义指令不走宿主命令注册表:因此不出现在「命令」分组、也不产生
command/run日志;执行要求该会话有活跃代理(冷/持久化未恢复的子代理会话返回 404 提示)。选中即发送,不影响输入框里已有的草稿。 - 自定义指令 Prompt 含技能令牌:发送的消息打
{ kind: "user" }源,与手打同待遇——映射技能令牌会由宿主注入全文,原生技能令牌由官方手势边界注入。 - 与原生技能重名:两路注入都会发生(罕见;映射侧按本插件口径再注入一次)。
- 注入不回放:注入发生在用户消息进入的那一步;会话恢复后历史里的
/名字令牌保留但正文不重复注入(与原生手势一致)。 - 只扫一级子目录:更深的目录不索引(与需求一致)。
- 快捷按钮的官方槽位口径:
conversation.composer.dock官方注释建议「可点击控件放工具行」——那是针对环境读数的风格建议,非技术限制;按钮网格装不进一行高的工具行,本插件知情采纳该槽位(几何上正是「输入框下方」)。 - 「与输入区同宽」是条目自我约束(对齐锚点 C 定稿):dock 槽契约给的是宽度列,渲染器对 list 条目 Fragment 直出(无 DOM 包裹层)——网格以
max-width: var(--dsh-chat-content-width)+margin: 0 auto自我约束,左缘与输入文本左缘重合(官方 stats 行同款宽度策略);列minmax(96px, max-content)+ 单钮max-width: 220px,按钮随内容收缩、自左聚拢,长名封顶截断。 - 新增会话首屏借用
conversation.input.dock渲染 hero 网格:官方没有「hero 卡片下方」的座位,且conversation.composer.dock渲染点被!hero硬门控;conversation.input.dock渲染点只看输入区存在、不看 hero,blank-hero 下照样渲染——本插件以它注册 hero 条目(idv-quick-buttons-hero、order 0),用 CSSorder把网格排到 hero 卡片正下方,组件按 owner 份额session.composerPhase !== 'blank'返回 null、与会话态的 composer.dock 条目互斥。迁移门:上游!hero门控对 stats 行本属过度保守,若放宽为zone !== void 0,删除 hero 条目即可一行迁回 composer.dock(文本列对齐已由锚点 C 保证)。 - 快捷按钮的悬停提示用原生
title:官方 primitives 的Tooltip需要子元素 ref 转发契约(React 18 函数组件不满足),按约定退化为原生title展示完整 Prompt。 - composer 被其他插件 block 时按钮仍可点击:dock 的 owner 份额拿不到 block 信息;此时追加作用在暂时不可见、不可提交的草稿上,block 清除后可见、可撤销,无破坏性。
- 子代理会话的输入框下方同样出现按钮网格:dock 是 session 作用域,配置是全局的——接受此行为。
- 样式覆盖依赖文档序:
.v-qb-*的自定义能力来自「插件 style 标签追加在 head 末尾、同特异性后到者胜」;若官方未来引入更高特异性或@layer,需把钩子类升级为双类特异性。
从源码验证
pnpm check # node --check 两个入口
node test/scan-smoke.mjs # 配置归一化 / frontmatter / 扫描去重
node test/inject-smoke.mjs # 令牌匹配口径 / 消息过滤 / 渲染形状
node test/settings-schema-smoke.mjs # 设置 schema 契约(可调用 / toJSON / walker 形状 / 分层合并)
node test/buttons-schema-smoke.mjs # 快捷按钮 schema(归一化 / 去重 / autoSend / 上限 / 分层合并)
node test/settings-card-smoke.mjs # 设置节/标签页契约(v-skills / 三 tab / 旧卡片移除 / 暂存-保存 / 重置 / dock 注册)
node test/buttons-reorder-smoke.mjs # 快捷按钮排序(moveButton 纯函数 / 控制器暂存口径 / 拖拽与上下移交互胶水)
node test/host-apply-smoke.mjs # apply 接线 + /list /skill /run 端到端 + pre-step 注入 + 设置热切换 + buttons 节
node test/client-shape-smoke.mjs # 客户端 bundle 形状(factory / 触发源 / 设置卡片 / dock 契约 / 模块声明)
node test/quick-buttons-client-smoke.mjs # 快捷按钮客户端(appendPrompt / 投影 / store / 点击语义 / 禁用)
License
MIT
No comments yet. Be the first to write one.