deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
为 DeepSeek Harness(DSH) Web 客户端新增的「Skill 选择按钮」:在输入框「+」按钮右侧,让用户为下一条消息选择要调用的 Skill(不选 / 单选 / 多选),并支持把一个本地 .md / .txt 文件临时注册成本会话可用的 Skill。
deepseek-ai/deepseek-harness(DSH 是 DeepSeek 官方项目,本仓库只承载这个插件作为补丁提交)@deepseek-ai/dsh-client-ui-skill-pickerdsh/
packages/client/ui-skill-picker/ ← 新增插件包(node 半边 + 浏览器半边 + Remote)
.agents/notes/implemented/feature/ ← 本功能的 Agent Note(中英)
dsh-patch/after/ ← 需要在 DSH 中修改的 8 个文件(改后内容)
dsh-patch/CHANGES.patch ← 上述文件的统一 diff(生成后写入)
MANIFEST.sha256 ← 所有文件的校验清单
dsh-patch/after/ 里的文件是改后全文,对应的仓库相对路径就是它在 after/ 下的路径。
应用方式:把 after/ 下的文件覆盖到你的 DSH 检出对应路径;或直接应用 CHANGES.patch。
| 需求 | 实现 |
|---|---|
| 「+」右侧新增按钮 | 注册一个 conversation.input.left list cell —— 该 slot 本来就渲染在「+」紧右侧,因此没有改动 ui-conversation 一行代码 |
| 折叠栏(展开/关闭/点外/Esc) | 面板锚定在 composer 卡片上沿(与 / 菜单、popupSelect 同一套 bottom: calc(100% + 4px) 规则),外卖点击落在卡片内则不关闭 |
| 列表 + 搜索 | 数据来自现有 skills/list Remote(按会话 single-flight 缓存),搜索按名称/描述做有序子序列匹配 |
| 0/1/多选 | 状态是 selected: string[],点整行切换 |
| chips | 已选项显示为 chips,可单独移除、可一键清空 |
| 按钮高亮 + 数量 | data-active 属性选择器 + 数量角标 |
| 添加本地 Skill 文件 | 折叠栏底部入口 → 解析 front matter → 经本包自有 Remote 注册为该会话作用域的运行时 Skill 并自动选中 |
| 临时 Skill 仅本会话、发送后取消选中但保留在列表 | 注册落在 Agent scope layer;发送成功后清空选择,行仍留在列表 |
| 请求携带已选 Skill | 选择被写成正文里的 /name 手势(dsh-tool-skill 的 agent/pre-step 已解析并注入正文),不新增第二条注入通道 |
/name 手势而不是新增字段Host 的 dsh-tool-skill 的 agent/pre-step 会扫描已认领用户消息里所有 /<name> token,逐个解析并把 renderSkillContent 结果作为 instructions 注入。这条既有边界天然满足:多个 Skill 随一条消息发出、disable-model-invocation Skill 可达、"只影响下一条"(发送即清空草稿)。所以本插件只做 UI + 把选择变成手势。
前置:DSH 源码检出、Node ^22.19 || >=24、pnpm。
# 1. 复制插件包
cp -r dsh/packages/client/ui-skill-picker <DSH>/packages/client/ui-skill-picker
cp -r dsh/.agents/notes/implemented/feature/2026-09-19-composer-skill-picker*.md \
<DSH>/.agents/notes/implemented/feature/
# 2. 应用对既有文件的修改(8 个文件)
cd <DSH> && git apply /path/to/CHANGES.patch
# 3. 安装依赖(新增了 zod 与两个 peer,必须装)
pnpm install
# 4. 构建 + 启动
pnpm run build:lib # host + client 两个构建面(生成 typert 产物)
pnpm run build:web # 刷新前端 dist
pnpm run start:desktop
开发期热更:pnpm run dev:web 配合 pnpm dsh web。
| 文件 | 改了什么 / 为什么 |
|---|---|
packages/api/remotes/src/client/index.ts |
把本包的 Remote contribution 加进 Client 装配列表(仓库里所有 namespace 的统一挂载点) |
packages/api/remotes/package.json |
声明对 @deepseek-ai/dsh-client-ui-skill-picker 的依赖 |
packages/bundle/web-app/cordis.patch.yml |
新增 ui-skill-picker 浏览器插件行 |
packages/bundle/web-app/package.json |
声明该 bundle 对插件包的依赖(verify-cordis-config 要求) |
scripts/package-dependency-policy.ts |
PEER_REQUIRED_HOST_EXPORTS 增加 dsh-scope: createScope 与 dsh-skill: isSkillName(仓库规定此类新增须人工评审,PR 里需单列标题) |
scripts/verify-package-readme-model-experience.ts |
README 门禁的 allowlist 增加本包(kind: indirect) |
tsconfig.base.json |
手写路径别名(包名与目录名不同,生成器不覆盖) |
tsconfig.client.json |
Client 聚合 references 增加本包 |
/name 与选择器选中的在协议上不可区分。最初的实现用 file.text() 读取本地文件,一律按 UTF-8 解码。中文 Windows 编辑器默认写 GBK,于是 GBK 字节被解成 U+FFFD 替换字符并被当成 Skill 正文注册(表现为乱码)。
现在 src/client/domain.ts 的 decodeSkillText 以严格 UTF-8 解码器做判别器,失败则回退 GB18030,并剥离 BOM;tests/browser-plugin.client.spec.ts 里有对应的回归用例(UTF-8 不被改动 / 真实 GBK 字节正确解出中文 / 带 BOM 仍能解析 front matter)。
| 项 | 结果 |
|---|---|
tsc -b 两个 face |
通过 |
tsdown host / client 两面 |
通过,产出 typert.host.js / typert.remote-client.js / client.js |
| 真实浏览器(Edge + Playwright) | 按钮存在、折叠栏可展开、搜索框与底部入口就位、列表渲染真实 Skill、Console 零报错 |
| 纯函数与编码回归用例 | 通过(16 个用例中 11 个) |
| 插件注册类用例 | 未通过 —— 原因见下 |
sections/scripts/test-invariants.ts(Vitest 全局 setup)在 fiber.ctx.fiber.state 处抛
TypeError: Cannot read properties of undefined (reading 'PENDING'),使任何在 spec 中直接调用 ctx.plugin() 的用例失败。
对照证据:未修改的 packages/client/ui-plan/tests/browser-plugin.client.spec.ts 报完全相同的错误(4 个用例挂 2 个)。因此插件注册类用例的失败属于该全局 setup 与 ctx.plugin() 的兼容问题。
与 DSH 上游一致(MIT)。本仓库内容为对 deepseek-ai/deepseek-harness 的补丁贡献。
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。