Skip to main content
Z

dsh-groups

zyfsyx666/dsh-groups

Group installed DSH plugins into named sets and enable, disable or exclusively apply them by rewriting the profile bundle list. Can also render a set as an agent preset that a single conversation uses.

Install

dsh plugin --profile web add github:zyfsyx666/dsh-groups

README

dsh-groups

给 DeepSeek Harness 装一个插件分组一键开关。

DSH 的插件是否加载由 profile 的 package.json → dsh.profile.bundles 决定,改完必须重启才生效。 这个插件把这个清单编成几套命名组合,点一下整组切换,不用再手动编辑 JSON。

安装

从 GitHub 直接装(还没发 npm,用本地路径即可):

# 1. 克隆到 DSH 的插件源码目录
git clone https://github.com/zyfsyx666/dsh-groups.git ~/.dsh/plugins-src/dsh-groups

# 2. 在 profile 里登记
cd ~/.dsh/profiles/desktop
#    dependencies 加一行: "dsh-groups": "file:../../plugins-src/dsh-groups"
#    dsh.profile.bundles 加一行字符串: "dsh-groups"

# 3. 装依赖
pnpm install --prefer-offline

# 4. 重启 DSH

Windows 上路径是 C:\Users\<你>\.dsh\plugins-src\dsh-groups,profile 在 C:\Users\<你>\.dsh\profiles\desktop。

装好后左侧栏多一个「插件分组」条目(order 60)。

面板

侧栏「插件分组」(order 60)。打开后:

  • 顶部显示当前生效的分组(可能同时有好几组;一组都没满足时显示「尚未启用任何分组」)。
  • 每张卡片显示点它之后会新增多少 / 已装但清单里没有的包 / 会顺带关掉哪些不属于任何分组的插件。
  • 「启用这组」→ 只做加法,把这一组的插件并入 profile 的 dsh.profile.bundles,你正在用的其它插件不会被关掉。
  • 「仅保留这组」→ 覆盖,关掉其它所有插件只留这一组。
  • 「停用」→ 减法,只把这一组加进来的插件拿出去;别的组(含常驻组)不受影响。确认框里会先告诉你移除几个、是哪些。
  • 三处都会提示重启 DSH。
  • 「新建分组」/「编辑」→ 勾选式界面,从全部已装插件里挑,带搜索;分组里存着但已经不在目录里的包会单独列出,可以一键移除。
  • 「预设」→ 把这一组写成一个 agent 预设,新建会话时可以选它单独生效(见下节)。
  • 「删除」→ 二次确认。

写盘前会备份:package.json.bak-before-groups-<时间戳>、cordis.patch.yml.bak-before-groups-<时间戳>。

多组叠加

两个开关决定一个分组怎么和别的分组共存:

标记含义典型用途
常驻(persist)应用任何别的分组时,这一组会自动一起叠加上去「常驻基础」——字体、通知、顺滑流式这类天天要开的
兜底(solo)不吃任何叠加,应用它就是干干净净只有它自己「全关」——排查问题时关掉一切
  • 点「启用这组」= 加法:当前清单 ∪ 这一组(所有常驻组也会一起叠上来)。
  • 点「仅保留这组」= 覆盖:只剩这一组 + 核心,其它插件全关。
  • 点「停用」= 减法:只减掉这一组自己 enable 里的插件,跳过核心包、官方包、以及任何常驻组也在用的包。
  • 勾两张卡上的多选框 → 出现组合条 → 「同时启用这两组」= 两组一起加进来(A 和 B 一起执行)。最多同时勾两个。
  • 卡片上会提示「应用这一组时会叠加:base + taskA」,点之前就知道会上去什么。

叠加只影响选哪些插件;最终清单仍是扁平的一层 dsh.profile.bundles,顺序保持用户原有顺序、新项追加在尾部。

两个标记都在编辑器里勾(「设为常驻组」/「设为兜底组」),也可以直接写 plugin-groups.json 的 persist / solo 字段。都不勾就是普通任务组。

保存时没带的标记沿用旧值,所以用脚本 PUT /group 只改 enable 不会把兜底标记抹掉;要显式清除就传 solo: false。

单个对话只开一组(预设)

点卡片上的「预设」按钮,就能把这一组写成一个 agent 预设:

  • 勾「生成预设」+ 选能力 → 保存 → 重启 DSH → 新建会话的预设列表里出现 group-<分组id>。
  • 选它开的新对话,只带这一组勾选的官方 agent 能力(基础工具 / 计划模式 / 上下文压缩 / 子代理 / 联网 / 定时任务)。
  • 取消勾选「生成预设」就把它从补丁层里移除(受管块整体重写,不会留残渣)。

常驻组的能力会自动并进来:如果你有标了「常驻」的分组(persist: true),它勾的能力会 自动加进其它每一组的预设——常驻组的语义就是「任何场景都要有」。所以给「任务 A」开预设时, 常驻组勾的「定时任务」也会一起写进去,不用重复勾。预设编辑器里会明说「常驻组也会带上它们勾的 能力:常驻基础(定时)」。兜底组(solo: true)不吃这套,它的预设有且只有自己勾的能力。

id 撞车会自动区分:分组 id 会折成预设 id(a: b → group-a-b)。如果两个分组折出同一个, 后一个会带上稳定后缀,例如 group-a-b--ec916c(原始 id 的 sha1 前 6 位)——不是随机数, 所以同样的分组永远得到同样的 id,重复同步不会反复重写。

产物写在 <profile>/cordis.patch.yml 里的一段受管块,形如:

# >>> dsh-groups presets (managed) >>>
- insert:
    - id: preset-group-task-a
      name: '@deepseek-ai/dsh-agent-preset'
      config:
        id: group-task-a
        order: 100
        name: 任务 A
        plugins: [ ... ]
# <<< dsh-groups presets (managed) <<<

块外的手写补丁不动;内容没变时不会写盘;写之前留 .bak-before-preset-<时间戳>。

两个安全阀:

  • 受管块必须有完整的首尾标记,而且只能有一份。缺结束标记、或者出现第二份,同步会直接拒绝(HTTP 409 BLOCK_CORRUPT)并保持文件逐字节不动——宁可不动,也不吞掉块外你手写的补丁。
  • 卸载插件之后的残留项:分组里引用了已卸载的包时,卡片会提示「未安装(会被跳过)」,点「编辑」可以在清单顶部看到逐条列出的残留项,每条带一个 × 按钮单独删掉(也有「全部移除」一次清空)。
    • 标签分两种:「(未安装)」是真的装不上,保存时会被丢掉;「(不可选)」是内置官方包(@deepseek-ai/* 由宿主自带、不进 profile 的 dependencies),保存时照常保留。判据来自后端 GET /state 的 missing 字段(走 isInstalled()),前端不自己猜。
    • 如果当前 DSH 进程是旧版、/state 里还没有 missing,标签会显示「(未知)」并提示重启,而不是把官方包误标成「未安装」。
  • 分组 id 只允许字母、数字、点、下划线、连字符(1–64 位)。像 a: b 这种会折成 a-b 生成预设 id,避免把整份 cordis.patch.yml 写成解析不了的形态。
  • 历史遗留的脏 id 能救回来:旧版本写进 plugin-groups.json 的不合规 id(如 a: b)会被标成 legacyKey,卡片上给出警告,点「编辑」可以把 id 改成合规形态并保存(按钮文案变成「改名并保存」);这类分组也能正常删除。改名时目标必须是合规 id、不能撞上已有分组。

没开任何预设时:如果块里本来就空,同步直接返回「无变化」,不写盘,也不会凭空创建 cordis.patch.yml。

删了分组不忘清理:删除分组时会顺手重同步一次,它生成的预设会一起消失。如果补丁层里还留着没有任何分组对应的预设,面板顶部会提示,并给一个「清理孤儿预设」按钮。

老进程上不白屏:如果当前 DSH 进程还没加载到带预设能力的这一版(GET /presets 返回 404),面板会明说「重启 DSH 后按钮才可用」,分组的启用 / 停用仍然照常工作。

两条硬限制:

  1. 预设只管 agent 平面。第三方插件是宿主平面(挂 webServer/llm/systemPrompt/skills), 工具在全局注册,塞进预设会触发 Preset services require isolate realms 检查失败。 所以「这个对话装载哪些第三方插件」仍然只能靠顶部的分组开关(要重启)。
  2. 预设只在会话空白时能换。官方判据是「还没发过任何消息」,第一句话发出去后就锁定(agent-preset/locked)。

永远保留的核心

@deepseek-ai/dsh-base、@deepseek-ai/dsh-web-app 以及官方 17 个受保护模块,加上 dsh-groups 自己、dsh-m、dshmarket 不会被任何分组关掉。

官方包不进勾选清单:@deepseek-ai/* 全部不列在插件选择里,分组也不会开关它们。 应用分组时,当前已启用的官方 bundle(dsh-experimental-agent-team-profile、 dsh-experimental-auto-review、dsh-experimental-voice-input-bundle 等)会被原样保留。 要开关这些,请用 DSH 自带的插件管理器。

为什么需要重启

DSH 的 HMR 只监视显式配置,node_modules 与 profile manifest 不在监视范围内(hmr 行 config.root = [])。 所以本插件不做热重载,只负责把清单改对,重启由用户自己决定何时做。

数据文件

  • 分组定义:<profile>/plugin-groups.json(不内置任何预设分组,首次打开是空的,由你自己建)
  • 日志:~/.dsh/dsh-groups/plugin-groups.log(超 1 MB 轮转为 .1)
  • 锁:<profile>/package.json.lock、<profile>/cordis.patch.yml.lock(写到锁里带 pid + 实例 id,可接管死进程留下的锁)

安全

所有 HTTP 路由只接受回环地址,且要求 Host 回环、Origin 同源、Sec-Fetch-Site 不是 cross-site。

测试

npm test        # check + check:hooks + 扫描器自检,三段串跑
npm run check   # 语法 + package.json 合法性
npm run check:hooks   # 只跑 hook 位置扫描

check:hooks 挡的是 node --check 结构上抓不到的一类问题:hook 写在了组件的 return 之后。1.4.4 就是这样白屏的——第一次渲染走 early return 只跑了 15 个 hook,state 到货后跑到第 16 个,React 抛 #310 把整个面板判崩溃,而语法完全合法。

判定标准是「hook 必须位于该组件所有 return 之前」。回调里的 return 不算,因为它不属于组件本身:

const save = async () => {
  if (!confirmDialog('覆盖?')) return   // 这是回调的 return,不算组件的早退
}
if (state === null) return h('div', null, '加载中…')   // 这才是

识别范围:组件认 function Name(…)、function* Name(…)、const Name = (…) =>、const Name = function (…)(含 async);hook 认 react.useXxx( 与解构引入后的裸调用 useXxx(;文件里自己声明的 useXxx 不算 hook。

--selftest 用内置样本自检扫描器本身(真违规必须报、回调 return 必须不报、箭头组件必须被识别……共 6 项),防止它哪天退化成只认字符串位置——那种检查比没有还危险。

Related plugins