Skip to content
dsh.fish
Bundle

dsh-plugin-desk

DeepSeek Harness 插件管家:在「设置 → 插件」新增标签页,用人话说明每个插件是做什么的,提供一键停用/启用(热生效)与安全卸载;同一页的「技能」分段盘点所有技能、标明唯一可用的触发方式,并给出两个真开关(模型自动加载 / 斜杠命令)。Plugin and skill manager for DeepSeek Harness: plain-language catalogs, one-click hot enable/disable, rollback-guarded uninstall, and per-skill invocation switches.

Source
Amouren7
License
MIT
Updated
Updated 14 hours ago

Readme

# dsh-plugin-desk · 插件管家

> 用人话看懂每个插件和技能是做什么的,一键停用/启用,安全卸载。

DeepSeek Harness 的「设置 → 插件」页是一份**只读**清单:它列出模块名,却不告诉你这个插件到底干什么用;想关掉一个插件,只能去手改 `cordis.patch.yml`。技能那边更糟 —— 装了 57 个技能,其中 14 个**模型根本看不到**,只能靠记住 `/handoff` 这类命令来用。

这个插件在同一个「插件」页里加一个**插件管家**标签页,分「插件」「技能」两段,补上这些事。

## 四个痛点

| 痛点 | 官方界面 | 插件管家 |
| --- | --- | --- |
| 看不懂插件是做什么的 | 只显示英文模块名 | 中文名 + 一句话说明 + 来源徽章 |
| 关不掉 / 卸不掉插件 | 纯只读,「已启用」只是个标签 | 一键停用/启用(热生效)+ 卸载 |
| 不知道技能怎么用 | 模型目录与 `/` 菜单互不可见 | 每个技能标明**唯一可用**的触发方式 |
| 不知道技能怎么关 | 没有开关,只能手改 SKILL.md | 两个真开关(模型自动加载 / `/` 命令),热生效 |

---

# 插件管理

## 功能

### 1. 用人话说明每个插件的作用

按质量优先级取说明:`dshhub.summary` → `package.json` 的 `description` → `README.md` 首段(自动剥离 Markdown / HTML、按语义边界截断)。

实测覆盖率(本机 168 个已安装包):`dshhub` 仅 1 个包使用,`description` 覆盖 164 个 —— 所以 `description` 才是主力,`dshhub` 是锦上添花。

### 2. 来源徽章

| 徽章 | 判定依据 |
| --- | --- |
| 官方 | 包名在 `@deepseek-ai/` 命名空间下 |
| 社区 | lockfile 里是普通 semver 版本号 |
| GitHub 直装 | lockfile specifier 为 `github:owner/repo#tag` |
| 本地链接 | specifier 为 `file:` / `link:` |

判定读 `pnpm-lock.yaml` 的 `importers` 段,**不看目录类型** —— 本环境用 `nodeLinker: hoisted`,真实目录并不代表来自 registry。

### 3. 一键停用 / 启用

走 Cordis 官方的 entry 更新通道 `Entry.update({ disabled })`:停用时 loader 会 `await _dispose(fiber)`,该插件的工具、路由、监听立即注销;启用时重新 `init()` 装配。**热生效,不需要重启。**

选择会记在 `~/.dsh/plugin-manager.json`,每次启动自动重放,所以重启后仍然保持你选的状态。

### 4. 安全卸载

- 先快照 entry 的 options,再 `loader.remove()`,然后**校验确实移除**;若仍在,按快照 `loader.create()` 恢复原状。
- 官方 `@deepseek-ai/*` 组件**禁止卸载**(卸载会破坏启动),UI 上按钮直接置灰,只允许停用。
- 卸载后同样记入状态文件,重启不会自己装回来。

### 5. 噪声过滤

本机装了 168 个包,其中绝大多数是传递依赖,真正能管理的插件只有个位数。列表会自动剔除:

- 非插件库(没有 `dsh.bundle` 的普通依赖)—— 实测过滤 154 个
- 框架内部条目(`cordis:include`、`node:*` 等)—— 实测过滤 4 个

---

# 技能管理

插件管家页顶部有 `插件 | 技能` 分段控制。技能页复用同一套卡片语言。

### 1. 每个技能标明「唯一可用的触发方式」

技能的 invocation 策略有两个互相独立的维度,界面把它们翻译成一句人话:

| 模型自动加载 | `/` 命令可用 | 界面直接告诉你 |
| --- | --- | --- |
| ✓ | ✓ | 模型会自己调用;也可用 `/name` 手动唤起 |
| ✗ | ✓ | **模型看不到它** —— 只能用 `/name` 手动唤起 |
| ✓ | ✗ | 仅模型自动调用(`/name` 不可用) |
| ✗ | ✗ | 已停用 |

第二行是本机 14 个技能的真实状态。它们在模型侧完全不存在,靠翻文件夹是发现不了的。

实测(本机 60 个技能):

```
模型看不到、只能用 / 唤起 = 14
两个开关都开的           = 46
来源:用户安装 57 / 插件提供 3
```

### 2. 两个真开关

不是「障眼法」:插件注册一个 **rank 0 的影子 provider**,把策略按需置 false。注册表按层合并、同层内 rank 升序取胜,而三个消费点都按策略过滤:

| 消费点 | 过滤 | 后果 |
| --- | --- | --- |
| `tool-skill` 模型目录 | `filter(isModelInvocable)` | 不进 `<available_skills>` |
| `tool-skill` 加载守卫 | `isModelInvocable` | `skill` 工具拒绝加载 |
| `session-controller` 命令目录 | `filter(isUserInvocable)` | 不进 `/` 菜单 |

**关键实现细节**:这些技能住在 **agent preset 作用域**,不在全局层。而服务读取走 topology-sensitive 属性代理 —— `ctx.skills`(声明式注入)返回绑定到访问上下文的反射,`ctx.get('skills')` 读的是全局存储。插件必须为每个 preset `createScope` 并走该作用域的 `.skills` 反射注册,否则候选会被更近的层直接盖掉。**这一步不需要修改任何预设文件**(shipped 与用户预设一并覆盖)。

### 3. 清理线索

| 标记 | 实测数量 | 判定依据 |
| --- | --- | --- |
| 目录内有**不生效**的 SKILL.md | 40 | 加载器只认 `<技能名>/SKILL.md`,`segments.length > 2` 一律忽略 |
| 目录内混入非技能文件 | 2 | 根目录出现 `package.json` / `package-lock.json` 等 |
| 缺少描述 | 0 | 模型与命令菜单都无法判断用途 |

嵌套那份不只是冗余 —— 抽样确认它与生效那份**内容不同**(`brainstorming` 10047 vs 15456 字节)。

### 4. 来源与升级对象

从指令文件路径反解归属:

- 路径含 `node_modules/<pkg>/` → 「插件提供 · 升级对象 `<pkg>` v`<version>`」
- 路径在 `~/.agents/skills/` 或 `~/.dsh/skills/` 下 → 「用户安装」

> `source` 字段在本部署**不可信**:host 进程 cwd 是家目录,预设把用户根报成 `project-agents`。所以分类以路径为准。

### 5. 没有「自动升级」按钮

实测本机 57 个技能:**0 个是 git 仓库**、无清单、无安装元数据(`~/.agents/plugins/marketplace.json` 与技能无关,它只有一条指向不存在路径的条目)。通用自动升级路径不存在,因此不提供按钮 —— 只标注该升级什么。

---

## 安装

```powershell
dsh plugin --profile web add "github:Amouren7/dsh-plugin-desk#v0.2.1"
```

装完重启 `dsh web`,然后打开 **设置 → 插件 → 插件管家**。

> 页面是懒加载的,浏览器可能缓存旧前端;若看不到新标签页,按 `Ctrl+Shift+R` 强制刷新。

> 暂未发布到 npm:`dsh-plugin-manager` / `dsh-plugin-hub` / `dsh-plugin-console` 三个相近名字均已被他人占用,
> 因此先用 GitHub 源码安装。本包名 `dsh-plugin-desk` 在 npm 上是可用的。

## 兼容性

| 项 | 值 |
| --- | --- |
| DSH | `>=0.1.5-rc.1`(依赖 0.1.5 的 `settings.plugins.tab` 插槽) |
| Node | `>=20` |
| Profile | `web` |
| 实测环境 | Windows 11 · DSH 0.1.5-rc.1 · Node 24.18.1 |

技能功能需要部署提供 `skills` 与 `agentPresets` 服务,以及可解析的 `@deepseek-ai/dsh-scope`;缺任一项时技能开关会如实拒绝并在界面说明原因,插件页其余功能不受影响。

`@deepseek-ai/dsh-scope` 从**两个**解析根依次尝试:profile 根(`<DSH_HOME>/profiles/`)与 DSH 自身的安装根。任一可用即可,因为预设作用域覆盖要靠它建立;两个都不可用时才会降级,且失败信息会列出实际找过的路径。

## 自检与测试

```powershell
node scripts/selftest.mjs   # 离线复验插件元数据链路(不启动 loader)
npm test                    # 22 项单元测试:路由自愈 / 客户端错误硬化 / 技能派生 / 作用域解析 / apply 冒烟
```

技能功能的端到端实测记录见 [`tests/acceptance.md`](tests/acceptance.md)(含**未验证项**的如实标注)。

## 已验证(全部为实测结果)

界面(`设置 → 插件 → 插件管家`):

| 验证项 | 结果 |
| --- | --- |
| 标签页接入 | 与官方 `插件配置`/`插件列表` 并列出现,官方页面 `data-slot-error` 为空 |
| 插件卡片 | 5 张卡片,名称/说明/来源徽章/版本/状态齐全 |
| 噪声过滤 | 168 个已安装包 → 只保留 5 个真插件,过滤 154 个非插件依赖 + 4 个框架内部条目 |
| 分段控制 | `插件 | 技能` 两段切换,切回插件段 5 张卡片无回归 |
| 技能卡片 | 60 张,含作用、`whenToUse`、来源、指令文件路径、用法提示 |
| 技能开关 | 60/60 可开关;写入后赢得该名字的 provider 变成本插件 |
| 界面一键停用 | 点「停用」→ `fiber 已 dispose`,徽章变「已停用」,按钮变「启用」 |
| 界面一键启用 | 点「启用」→ `fiber 重新装配完成`,徽章回「运行中」 |
| 状态持久化 | `~/.dsh/plugin-manager.json` / `~/.dsh/plugin-desk-skills.json` 落盘,启动时重放 |

**系统级验证技能停用真的生效**(由 harness 自身确认,非插件自证):

| 动作 | 本会话 `<available_skills>` 目录的变化 |
| --- | --- |
| 设 `brainstorming` 模型自动加载 = 关 | `brainstorming` **消失** |
| 恢复默认 | **回来** |
| 设 `grilling` 模型自动加载 = 关 | `grilling` **消失** |
| 恢复默认 | **回来** |

覆盖跨插件重载存活(开机即装,非懒装)。

宿主能力(HTTP 接口层):

| 验证项 | 结果 |
| --- | --- |
| 元数据解析 | 5 个插件全部解析出中文名与说明;来源判定 `github`/`community`/`linked`/`official` 全部正确 |
| 安全卸载 | 移除 → 校验确认 → 该插件路由端点随之失效(证明真 dispose)→ 记忆落盘 |
| 官方组件保护 | `@deepseek-ai/*` 卸载被拒,只允许停用 |
| 技能盘点完整性 | 60 个技能;与独立文件系统扫描(57 个目录 / 14 个模型不可见)逐项吻合 |
| 预设作用域安装 | 连续三次读取 `installed=[standard,ptc,minimal,cordis]` 稳定不变 |
| 解析根回退 | 人为移除 profile 根的 `@deepseek-ai/dsh-scope` 后:旧代码 `installed=[]` 且报「不可解析」,新代码仍为 `installed=[standard,ptc,minimal,cordis]`;恢复后无差异 |

## 实现说明

- **零构建**:`lib/*.js` 既是源码也是产物,没有打包步骤,改完直接生效。
- **文件职责**:`lib/index.js` 插件清单与 API;`lib/skills.js` 技能盘点、派生函数与覆盖 provider;`lib/skill-overrides.js` 可按预设单独装配的入口;`lib/client.js` 前端。
- **宿主侧零依赖**:只用 Node 内置模块。技能作用域需要 `@deepseek-ai/dsh-scope` 时**动态解析并 import**(不是静态依赖,保住装配可移植性),并依次尝试 profile 根与 DSH 安装根两个解析根。
- **前端模块信封**:`lib/client.js` 按 DSH 的 client-modules 约定包成惰性 CJS 工厂(`window.__ModuleLoader__.load({ id, factory })`),模块体在首次物化时才执行。
- **React 取自 shell 的冻结模块表**:`require('react')` 即可,无需 import;但**必须在模块作用域解析一次**,放进组件体内会在渲染期炸掉整个 section。详见 [`docs/SLOT-CONTRACT.md`](docs/SLOT-CONTRACT.md)。
- **注册值是函数组件**:`SlotComponent<P> = (props) => ReactNode`。返回 DOM 节点或 `{ render() }` 对象会被 React 拒绝,并让官方插件页变成空白。
- **持久化为什么不写 patch 层**:bundle 装配会用自己的 id 组合出同名条目,patch 层的 `disabled:` 条目**并不能可靠阻断**它。因此状态由本插件自管,启动时重放。

## 权限与数据

| 项 | 说明 |
| --- | --- |
| 读取 | profile 的 `node_modules`(仅 `package.json` / `README.md`)、`pnpm-lock.yaml`、运行中的 loader 条目、技能注册表、技能指令文件所在目录(判断清理标记) |
| 写入 | `~/.dsh/plugin-manager.json`(插件停用/卸载记忆)、`~/.dsh/plugin-desk-skills.json`(技能开关覆盖) |
| 网络 | 无 |
| 凭据 | 不读写 |

技能开关**不修改任何 SKILL.md**:状态由影子 provider 覆盖,删掉状态文件即完全复原。

## HTTP 接口

界面之外,宿主能力也可直接调用:

| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/dsh-plugin-desk/api/list` | 插件清单(含中文名、说明、来源、状态) |
| POST | `/dsh-plugin-desk/api/toggle` | `{ entryId, enabled }` 热启停 |
| POST | `/dsh-plugin-desk/api/uninstall` | `{ entryId }` 安全卸载 |
| GET | `/dsh-plugin-desk/api/skills` | 技能清单(含用法提示、来源、清理标记、`presetInstall` 诊断) |
| POST | `/dsh-plugin-desk/api/skills/toggle` | `{ name, model?, user? }` 设置技能开关 |
| POST | `/dsh-plugin-desk/api/skills/reset` | `{ name }` 恢复该技能的默认策略 |

## 卸载本插件

```powershell
dsh plugin --profile web remove dsh-plugin-desk
```

状态文件 `~/.dsh/plugin-manager.json` 与 `~/.dsh/plugin-desk-skills.json` 会保留;若不再需要,可自行删除。删除技能状态文件后,所有技能立即回到默认策略。

## License

MIT

Install

dsh plugin --profile web add github:Amouren7/dsh-plugin-desk#92f1ec4e6e57001a2139902d62679efc55c32234

Profile: web

Source