dsh-plugin-model-dispatch
English | 简体中文
给主模型、子代理和计划模式各配一个模型与思考强度——就在聊天栏里,一个菜单搞定。
DeepSeek Harness 自带的是主模型选择器。本插件保留这个控件,补上它没覆盖的两条路由——子代理跑在哪个模型上、计划模式用哪个模型思考——并把三者放进同一个「看起来就是原生控件」的菜单里。
| 路由 | 设置入口 | 作用范围 |
|---|---|---|
| 主模型 | 聊天栏主芯片 · 设置页第 1 行 | 当前会话,走 DSH 原生切换接口 |
| 子代理模型 | 聊天栏子代理芯片 · 设置页第 2 行 | 本会话之后新启动的子代理 |
| 制定计划模型 | 设置页第 3 行 | 仅计划模式;退出计划后恢复进入前的主模型 |
目录
- 功能
- 安装
- 使用
- 聊天栏
- 设置页
- 计划模式
- 子代理
- 路由规则
- 持久化
- 兼容性
- 开发
- 故障排查
- 已知限制
- 致谢
- 许可
功能
-
两个芯片,穿的是宿主自己的材质。 输入框里一个主模型芯片(宿主原生模型图标)、一个子代理芯片(分支图标),只靠图标区分,悬停与无障碍标签仍是完整的
主模型 · 模型 · 强度。两个都是无边框 28px 芯片,只在悬停时浮起底色,和旁边的原生控件完全一致。子代理处于继承状态时显示 跟随主模型,不重复主模型的名称。 -
所有入口共用一个菜单。 模型/提供方搜索、提供方分组、收藏、最近使用、思考强度——聊天栏和设置页用的是同一个菜单。它采用宿主菜单的材质(半透明模糊面板、抬升阴影、4px 内边距、34px 行高、13px 字号、l2 发丝分隔),看起来是一方控件而不是插件浮层。
-
思考强度用离散滑杆。 每个档位一个刻度,最左额外多一档 提供方默认,让「不指定强度」成为看得见的状态而不是隐藏行为。刻度画在轨道下方(不会把蓝色填充切成一段段),当前档位名显示在标题行,支持拖拽与方向键。
-
即选即生效,没有确认行。 选模型、拖滑杆、恢复默认都是当场写入:拖动滑杆会在停手后合并成一次写入,写入串行化,失败会回滚该行并给出真实错误。菜单保持打开,选完模型可以直接接着调强度;
Esc或点击菜单外只是关掉菜单,不会撤销已经生效的选择。 -
计划模式自动切换。 进入计划模式切到「制定计划」模型;批准计划或
/plan off后,在第一条执行请求组装之前恢复进入计划前的主模型与推理档位。审批面板会显示将要恢复的模型。 -
按会话的子代理覆盖。 聊天栏的子代理选择作为该会话的持久覆盖保存,绝不改动设置默认;一键即可恢复为设置默认。
-
全部模型都能委派。 子代理路由读取宿主实时模型目录:即使 rc.2 在会话创建时捕获了旧的白名单,显式选模与
list_subagent_models在已有会话里依然可用。之后新增的模型在下一次委派即可用;被移除的路由与不支持的强度会在保存前就被拒绝。 -
保存前校验路由。 覆盖与创建子代理前,通过宿主
resolveCallConfig校验 provider/model/effort 的真实组合;无效组合当场报出真实原因,而不是等子代理运行到一半才失败。
安装
要求 DeepSeek Harness 0.2.0-rc.2(其他 0.2.x 版本需先做兼容性验证,见兼容性)。
dsh plugin --profile web add git@github.com:kolawong/dsh-plugin-model-dispatch.git
把 web 换成你实际使用的 profile(desktop 等)。安装后完全退出并重新打开 DSH:宿主半边需要重组合,只刷新页面只会加载新的客户端半边。
仓库不跟踪 lib/ 构建产物:git 安装会经包的 prepare 脚本自动构建宿主与客户端 bundle,npm 发布走 prepack 构建。本地 checkout 手动安装前需先执行一次 pnpm install && pnpm build。
手动安装(链接本地目录)
// /profiles//package.json
{
"dependencies": { "dsh-plugin-model-dispatch": "link:/path/to/dsh-plugin-model-dispatch" },
"dsh": { "profile": { "bundles": ["…", "dsh-plugin-model-dispatch"] } }
}
先在 checkout 里执行 pnpm install && pnpm build,再在 profile 目录下建立对应的 node_modules 链接(profile 目录是解析锚点),然后重启 DSH。
使用
聊天栏
输入框右侧有两个芯片,紧邻权限与预设控件:
| 芯片 | 作用 |
|---|---|
| 主模型(模型图标) | 走原生接口切换当前会话的模型;悬停提示里给出实际路由与强度。 |
| 子代理(分支图标) | 选择本会话之后新启动的子代理用哪个模型,存为会话覆盖;没有覆盖时显示 跟随主模型。 |
点开任意一个都是同一个菜单:搜索、提供方分组、收藏(★,悬停浮现,按浏览器记忆)、最近使用、思考强度滑杆,以及子代理路由专属的 恢复为设置默认 一行。
设置页
Web 设置 → 模型代理 里有四行,每行一句作用范围说明:
| 行 | 含义 |
|---|---|
| 新会话默认模型 | 写入原生 agent-default-model 项;在聊天里切换模型不会改动它。 |
| 子代理默认模型 | 供没有会话覆盖的会话中的子代理使用。 |
| 制定计划模型 | 计划模式激活期间使用。 |
| 子代理派发活跃度 | 三档滑杆(保守 / 均衡 / 积极),经系统提示词引导模型拆任务的倾向;均衡 = 不加额外引导。并行上限与嵌套深度由宿主自带的「设置 → 插件 → subagent」页管理。 |
计划模式
- 进入计划模式时,原子保存当前会话的
provider/model/reasoningEffort,并切到制定计划模型。 - 批准计划(或
/plan off)后,在第一条执行请求组装前恢复刚才保存的路由。 - 恢复失败可重试,不会把会话卡在计划模型上;审批面板会写明将要恢复的模型。
子代理
- 工具调用里显式指定了模型的委派保持原样——插件只补齐原生调用留空的那部分。
- 子代理芯片与设置默认只影响新启动的子代理;已在运行的保持原模型。
- 子代理会话本身不参与计划切换与子代理覆盖。
路由规则
| 决策 | 优先级(从高到低) |
|---|---|
| 子代理模型 | 本会话覆盖 → 设置默认 → 原生父模型继承 → 工具调用里显式指定的启动参数 |
| 计划模型 | 计划模式激活期间用制定计划模型 → 之后恢复为进入前的会话路由 |
| 主模型 | 会话内选择(原生接口) → 设置页的新会话默认 |
默认与会话相互独立:设置页保存的是新会话默认,聊天栏选择与计划切换只改当前会话。宿主适配层用 AsyncLocalStorage 隔离原生 selectModel 的「写部署默认」副作用,只拦这一条调用链。
持久化
| 数据 | 位置 |
|---|---|
| 设置默认(子代理默认 / 制定计划模型) | DSH profile 配置(volatile 字段——设置页或配置文件均可改) |
| 新会话主模型默认 | 原生 agent-default-model 项 |
| 会话子代理覆盖 | $DSH_HOME/storages/model-switch/session-overrides.json |
| 计划前主模型(恢复成功后清除) | $DSH_HOME/storages/model-switch/plan-main-models.json |
| 菜单收藏 / 最近使用 | 浏览器 localStorage(按浏览器,不同步) |
兼容性
- 宿主: DSH
0.2.0-rc.2。插件只走 0.2.x 的扩展面(volatileConfig+configForms、会话投影、会话选模接口、planMode),没有 patch DSH 核心,所以上游一键升级不受影响。 - 更新的宿主需要 ≥1.1.0。 自 1.1.0 起子代理路由同时包装三条原生启动路径——
start/startContinuable(rc.2)与startActivation(更新宿主)。在只暴露startActivation的宿主上,1.0.x 会被静默旁路为父模型继承。 - 客户端: 浏览器半边以 DSH 客户端 bundle(
lib/client.js)形式发布,依赖宿主半边已加载;首次安装后只刷新页面是不够的。
开发
pnpm install
pnpm test # vitest —— 69 个用例
pnpm build # tsc(宿主 + 客户端两个工程)+ tsdown 客户端 bundle
只改 UI 时可以只重建客户端:
./node_modules/.bin/tsc -p tsconfig.client.json && ./node_modules/.bin/tsdown
测试覆盖:路由合并优先级、会话覆盖存取与并发写入、计划边界的进入 / 恢复 / 重启恢复 / 拒绝回退、会话选模与默认写入隔离、路由校验拒绝无效组合、客户端保存竞态、旧会话目录适配、并发委派隔离。
> tsc -p tsconfig.json(宿主工程)按 profile 解析到的 @deepseek-ai/* 做类型检查。如果本包自己的 node_modules 里恰好存在这些包的第二份拷贝,两份拷贝的 branded 类型会不一致并报错;客户端工程、bundle 构建与测试不受影响。
故障排查
| 现象 | 原因 / 处理 |
|---|---|
| 安装后看不到芯片或设置项 | 宿主半边没有重组合——完全退出并重开 DSH(只刷新页面只会加载客户端半边)。 |
| 子代理始终用父模型 | 比 rc.2 更新的宿主需要 ≥1.1.0(见兼容性);另外确认没有停留在只暴露 start 的旧宿主上。 |
| 菜单里少某个模型 | 该提供方的目录没加载成功——菜单顶部会提示部分目录加载失败,提供方恢复后模型即出现。 |
| 保存时提示路由无效 | 该组合不在提供方的实时目录里;从它所属的提供方分组里重新选择再试。 |
| 改了设置但聊天没变 | 属正常:聊天栏选择与计划切换是会话级,设置页保存的是新会话默认。 |
已知限制
- 与 DSH 0.2.0-rc.2 内部实现耦合较紧(
configForms、会话投影、会话控制器、计划模式)——DSH 升级后请重跑测试。 - 计划边界的识别依赖原生
exit_plan_mode的返回措辞;DSH 改措辞会延后恢复(plan/mode事件是兜底)。 - 子代理会话有意不参与计划切换与子代理覆盖。
- 菜单收藏与最近使用按浏览器(
localStorage)保存,不属于会话或 profile。
致谢
- 原始设计与 0.1.x 实现:lincong1987/dsh-model-switch(MIT)——本插件在其基础上构建。
- 0.2.x 适配、计划模式切换、会话覆盖、路由校验与当前 UI 均在本仓库重写/扩展。
许可
MIT