前言¶
DSH(DeepSeek Harness)团队里正在用队友(teammate / 可继续的 subagent)跑长任务的人,迟早会碰到一个坑:队友被派发到某个模型上之后,那个模型的额度用完了,队友就卡死了。你在 GUI 里切自己的模型、改全局默认模型,它都不受影响;给它发“继续”,它会在同一条已耗尽的路由上再失败一次。
本插件做的就是这件事:给队友会话单独加一个模型选择器,让它在额度耗尽后能换一个还有额度的模型继续跑,不用注销重建队友,也不触动 Lead 会话和全局默认模型。插件由 wufeng0105 维护,属模型推理分类,本仓库当前星标为 0,发布在 npm 上的包名是 @wufeng0105/dsh-teammate-model-picker,当前版本 0.3.2,要求 dsh >=0.2.1-rc.5 <0.3.0。
下面介绍这个插件解决的问题、它的实现方式、安装与用法,以及需要注意的限制。
问题出在哪¶
DSH 的队友在被派发的那一刻,就把当时 Lead 正在使用的模型写进了自己的会话描述(durable descriptor)。此后每次唤醒——收到消息、恢复运行、冷恢复——都按这份描述里的模型路由,不会重新解析当前默认模型。
于是当队友被钉在一个已耗尽额度的模型上时,现有能力帮不上忙:Client 侧官方选择器对 subagent 会话直接返回空,Host 侧通用 Session 路由对 origin: "subagent" 的会话一律返回 session/agent-busy。也就是说,队友会话的模型座位是空的,你没有地方可以操作它。
更具体地说:
1、你在 GUI 里切换自己的模型,队友不受影响,因为它读的是自己那份描述。
2、你给队友发“继续”,它沿用同一条已耗尽的路由,结果还是失败。
3、官方的“切换模型”能力对队友会话是明确拒绝的,所以不存在“顺手点一下”的路径。
README 提到,拒绝是三层叠加的结果,缝隙在哪里这个插件的 DESIGN.md 里有完整论证,这里不展开。
插件做了什么¶
它是一个 DSH 插件 bundle,分两半,对接着上述两层缺口:
- Host 半边:把“每个队友的模型覆盖”存成插件 Config(
overrides),在队友 Agent 发布时(agent/created)为它安装与官方installModelSelection等价的三个 waterfall,覆盖agent/request的最终路由。 - Client 半边:在 composer 工具行的
conversation.input.right座位注册一个模型选择器,观感对齐官方选择器(两级菜单:模型 / 推理等级),并且只在队友会话里渲染。
四条硬约束值得单独列出来,因为它们决定了插件的边界:
1、不改任何官方包。
2、不引入新的 session 事件类型。
3、没有 override 时零介入——派发路由原样生效。
4、不动全局默认模型。
这四条意味着它没有侵入 DSH 的内核,渲染的时机也只在队友会话里,不会污染普通会话。
安装¶
插件已经发布到 npm,用包名安装即可。安装命令如下:
plugin_manager install_bundle target = @wufeng0105/dsh-teammate-model-picker
这一步做的是注册表形态的安装,装的是独立副本,之后你改源码不会影响到它。开发时也可以直接指向本目录,走 link 形态,改源码立即生效。两种形态生效的判定标准一致:返回 application: applied 且 warnings: []。
安装会写 profile 的 package.json(依赖、精确版本、dsh.profile.bundles)、pnpm-lock.yaml、pnpm-workspace.yaml,不会动用户配置层。README 明确标注,裸路径与 file: 形态在开发机上未复测,生产用请走上面的包名路径。
卸载命令:
plugin_manager remove_bundle target = @wufeng0105/dsh-teammate-model-picker
这里有三个层面要分清,别在排障时搞混:
1、安装/卸载 bundle,改的是 profile 的 package.json 里 dsh.profile.bundles。
2、bundle 的默认配置在源 cordis.patch.yml 里。
3、用户配置(也就是你实际选的模型)在用户 profile patch 层的 - id: teammate-model-picker 那一行。
所以清空用户配置不等于卸载 bundle,两者是分开的。
典型用法¶
装好之后,换一个队友的模型就是四步:
1、打开一个队友会话。
2、composer 工具行右侧会出现模型选择器,显示当前模型和推理等级。
3、点开 → 「模型」→ 选一个还有额度的模型。
4、给队友发“继续”。
改动立即生效,并且写入用户 profile patch 层,重启 Harness 后依然生效。如果之后想让它回到派发时钉下的那条路由,菜单里的「恢复派发路由」会删掉这个队友的 override。
Config.showPicker 是真正的开关:设为 false 时选择器不渲染,默认是 true。也就是说,如果你不想在任何队友会话里看到这个座位,可以在配置里关掉。
怎么验证真的生效了¶
静态部分有一条离线门,秒级跑完:
npm run check
它做的是静态与契约检查(身份、依赖方向、样式 token、打包清单、版本区间、文档预算)加上离线契约测试(Host fake-DSH、Client vm、负例自检)。负例的存在是为了保证每道门都有可能变红,不是永远绿灯。
环境检查分两步:
node tools/doctor.mjs # 版本区间 / row / 会话日志格式 / schema 协议自述
node tools/doctor.mjs --contracts # 打印本插件依赖的契约清单
真正的行为门需要一条真机队友会话来判定——换模型到底有没有把路由换过去,离线测不出来:
node tools/session-model-log.mjs <teammateSessionId>
它解压 session.v4.jsonl.zstd 并打印四类事件:subagent/descriptor(派发时钉下的路由)、model/selection(切换意图)、request/header(每次请求实际用的路由)、turn/end(含失败原因)。判定标准和历史实测记录在 REPORT.md 里。
适用场景与注意¶
适合谁:用 DSH 跑多 Agent 协作、把长任务派给队友、并且依赖具体模型额度的人。当你怀疑队友因为封顶或额度问题停摆,又不想重建会话时,这个插件补上了原本空白的座位。
几个必须知道的限制,README 里都是明说的:
1、选择器落在相邻座位,不是官方那个模型座位。视觉接近,但 tooltip、aria 和键盘 Tab 序列与官方不完全同源。
2、插件复制了官方 CSS,类名前缀是 tmp_,只用了主题 token。官方改版样式时需要同步;token 改名只会让外观退化,不会崩。
3、没有模型白名单,展示的是运行时目录全量。如果部署里启用了子代理模型白名单,两边可能不一致。
4、切换在活跃 turn 中生效于下一个 step,与官方 installModelSelection 语义一致,不中断进行中的请求。
5、对 agent 暴露的操作尚未接线:换模型目前只有 UI 路径,规范要求的 agent tool 还没注册,需要一次运行期 API 查阅才能接上。
另外要说明它的定位:这份实现是本地绕过,不是官方正解。官方修复到位之后,这个插件应该评估退役。具体哪几条该由官方修,见 REPORT.md 的「上游建议」。
结尾¶
这个插件的价值在于,它把“队友会话的模型座位”从一个明确拒绝的位置,变成了一个可以操作的位置,且没有改动任何官方包、没有改动全局默认模型。如果你正在用 DSH 跑队友会话并被额度耗尽卡住,安装一条命令即可,退出也是一条命令。
更多细节:
- 目录页:https://www.skillhub.cn/plugins/wufeng0105/dsh-teammate-model-picker
- GitHub:https://github.com/wufeng0105/dsh-teammate-model-picker
- 许可证:MIT