Zum Hauptinhalt springen
T

dsh-subagent-route-pin

tkaiz0406/dsh-subagent-route-pin

Registers a wrapper provider that gives delegated subagents their own LLM route — one-off spawns through provider.start, Agent Teams members through a hook on startActivation — while the main agent keeps its model. Ships a Settings section for picking that route, a per-expert override file, a capability flag reporting which seams the hook caught, and a readiness record written at mount.

Installation

dsh plugin --profile web add github:tkaiz0406/dsh-subagent-route-pin

README

dsh-subagent-route-pin

给被委派的子代理(专家团专家、Agent Teams 成员、单发子代理)单独指定 LLM 路由,主 agent 不受影响。

解决的问题:DSH 默认让子 agent 继承主 agent 的模型路由,于是「大脑 + 4 路子代理」挤在同一个 provider 账号额度里,summon_experts 看起来卡死。装上本插件后,主 agent 可以跑免费车道,子代理跑另一个账号/模型,两条额度队列互不阻塞。

适用面:按 provider 生效,不按专家名单。agency 里 331 个专家、你新建的自定义专家、以后升级新增的专家,只要还读 config.provider,都自动命中同一条路由。

它做什么

  • 注册一个包装 provider pinned-account,委托内置 spawn,在创建子 agent 前填入 agentOptions
  • 单发子代理走 provider.start():包装 provider 直接生效
  • 团队模式成员不经过 provider.start():在 subagents.startActivation()(当前契约)或 startContinuable()(旧名)上挂一层钩子,把路由填进 request.agentOptions(可用 teamInjection: false 关闭)
  • 覆盖团队成员自带路由(overrideMemberRoute,默认开):Agent Teams 会按自己的 memberLlmProvider / memberModel 先填好 agentOptions,只填「空槽」等于一个成员也钉不上;开着时钉住的路由优先,关掉则尊重调用方已选的路由(只补空槽)
  • 能力标志:钩子探测结果会发布成 { teamInjection: 'installed' | 'unavailable', enabled, seams, reason },调用方能读到「团队注入此刻可不可用、挂在哪条缝上」,而不是只靠日志猜
  • 零配置可用:没配路由、或路由指向一个当前不存在的 provider/model 时,子 agent 继承主 agent 的模型,召唤不会失败
  • 失败必提示(onPinFailure,默认 fail):你在设置页选过模型、它却跑不起来时,召唤直接报错并在错误里写明「哪个路由失败、为什么、去哪里改」,绝不静默换成别的模型;设成 inherit 才允许回退
  • 自动接线(autoWireAgency,默认开):挂载时把 profile 里 agency-agents 行的 provider 补成 pinned-account(改前留 .bak-autowire-* 备份,已接线则不动),装完不需要手改 YAML
  • 每次委派的结果写进 ~/.dsh/subagent-route-pin.last-run.json(pinned / failed + 原因),排障不用翻日志
  • 设置 → 专家模型 页面:提供商 / 模型 / 推理强度下拉 + 团队模式注入开关 + 专家团引擎提示,保存写回 profile 补丁,下一次委派生效
  • 逐专家覆盖:~/.dsh/subagent-route-pin.json 的 experts 字段(可选,优先级高于页面)

安装

# 从 tarball
dsh plugin --profile desktop add "./dsh-subagent-route-pin-0.8.13.tgz"

# 从 npm(发布后)
dsh plugin --profile desktop add "dsh-subagent-route-pin"

# 从本地目录(开发调试,link 安装)
dsh plugin --profile desktop add "D:\\path\\to\\dsh-subagent-route-pin"

dsh plugin 要写 profile 目录,通常需要提权(关闭沙箱或以同一用户直接运行终端)。

name 是 DSH loader 的模块解析键,必须与 package.json 的 name 逐字一致,并且真的能在 profile 的 node_modules 里解析到。两个约束同时成立才加载:

  • profile 的 cordis.patch.yml 按 name 匹配 cordis.yml 的基础条目,不一致整条被跳过(该行配置静默失效);
  • loader 拿这个字符串去 node_modules 里解析,解析不到就静默不加载——设置页那张卡片还在(客户端代码是单独扫描 node_modules 得到的,独立于宿主加载),但供应商/模型两个下拉是空的、推理强度停在第一项 low,也没有就绪记录。

「装上了但什么都没生效」基本都出在这。典型冲突:profile 用带 scope 的依赖键装(node_modules/@owner/dsh-subagent-route-pin),而包名是裸名 dsh-subagent-route-pin → 裸名解析不到。两种解法二选一:把依赖键改成包名本身,或在 node_modules 里给裸名建一个指向实际目录的 junction(Windows New-Item -ItemType Junction,不需要管理员)。

自检两行即可分辨:

node -e "require('dsh-subagent-route-pin/package.json')"   # 能解析 → 名字对
cat ~/.dsh/subagent-route-pin.load.json                   # 有这个 → 模块被 import 了(apply 不一定跑)
cat ~/.dsh/subagent-route-pin.ready.json                  # 有且 at 是本次启动 → 宿主真的激活了插件

装完后 profile 的 cordis.patch.yml 里新增一行(包内 cordis.patch.yml 已自带,无需手写)。provider 留空即「不钉路由」,子 agent 继承主 agent 的模型:

- id: subagent-route-pin
  name: "dsh-subagent-route-pin"
  config:
    providerName: pinned-account
    baseProvider: spawn
    provider: deepseek-account
    model: deepseek-flash
    reasoningEffort: ''
    strict: false
    teamInjection: true
    overrideMemberRoute: true
    fallback: true

装完不配也能用:路由没填、或填的 provider/model 在当前账号里不存在时,插件只打一条警告,子 agent 照旧继承主 agent 的模型——不会让召唤失败。strict: true 才会把它变成硬错误。

真正要改的只有两处

想把子 agent 分到另一条车道,只需要两处:

  1. 这条路由 —— provider / model,指向你账号里真实存在的组合。不用手改文件:打开 设置 → 专家模型,点选后保存即可(写回同一行)。
  2. 专家团那行的 provider —— 改成 pinned-account,否则专家根本不经过本插件。

除此之外没有第三处。团队模式(Agent Teams)不走这条路径,见下文。

让专家团用上它

专家团(@michengai/dsh-agency-agents)默认走 spawn,需要把它的 provider 指过来:

- id: agency-agents
  name: "@michengai/dsh-agency-agents"
  config:
    provider: pinned-account
    # 不要写 maxDepth —— 写了会强制单发阻塞引擎,召唤要等全部跑完

注意:profile 的 patch 行是整行替换 config,改这一行时要把 enabled / customExperts / enabledTeams 等原字段一起重述,否则会被清空。

Agent Teams(@nanmicoder/dsh-agent-teams)有自己的一套 memberModel / memberLlmProvider,插件不会覆盖它——两条路由各管各的,避免互相打架。

看团队跑到哪了(本地面板)

Agent Teams 自己是条件渲染的:会话标题栏那个入口只在「当前会话确实持有一个开跑的团队」时才出现,计划还在暂存(等你批准)、或者你换了会话、想看别的项目里的团队,界面上就什么都看不到。

插件带了一个只读的状态面板,直接读磁盘上的 .agent-teams/<team>/team.json:

# 起一个本地面板,浏览器打开它给的地址,2 秒自动刷新
npm run teams -- --scan "D:/你的工作区父目录"

# 只想在终端看一眼
npm run teams:once -- --dir "D:/你的项目"

# 装了 npm 包的话,也可以直接用 bin
dsh-team-status --serve --port 7800

参数:

参数作用
--dir <path>指定工作区根目录(其下有 .agent-teams/),可重复
--scan <root>把 root 自身和它的直接子目录里带 .agent-teams 的都扫进来
--serve起 HTTP 面板(默认 127.0.0.1:7788)
--port <n>换端口

面板显示:团队阶段、计划是否待批准、成员(角色/状态/模型)、任务表(ID/状态/负责人/依赖/尝试次数)、完成进度条。暂存态会直接提示「计划还没批准:去 Web 的计划卡点 Approve & Run,否则成员不会启动」——这就是最常见的「看不到团队」原因。

它只读状态文件,不连 DSH、不改任何东西,所以任何会话、任何项目都能看。

验证

重启 DSH 后:

  1. 设置 → 专家模型 页面出现,底部显示「专家团引擎」
  2. 日志出现 subagent-route-pin: provider "pinned-account" delegates to "spawn"
  3. 日志出现 subagent-route-pin: team-mode hook installed on ctx.subagents.startActivation
  4. 日志出现 subagent-route-pin: capabilities {"teamInjection":"installed","enabled":true,"seams":["startActivation"]}
  5. 委派时出现 subagent-route-pin: team member label="…" -> provider/model

这五行是接口契约,不是装饰:第 2 行说明 provider 包装还挂得上,第 3、4 行说明创建子代理的那条缝还在(seams 会写出实际包住的方法名),第 5 行说明注入实际生效。升级 DSH 后少任何一行,都说明上游接口变了,先别怪额度。

这些行在哪看:ctx.logger.info 只进 DSH 进程控制台(GUI 的开发者控制台 / 终端 stdout),不落盘。~/.dsh/logs/ 下只有启动失败时的诊断 dump,正常启动不写日志文件——所以别去那里找。

插件为此每次启动落一条就绪记录,升级后一条命令就能验:

npm run verify        # = node test/run.mjs && node test/logs.mjs --strict
npm run ready         # 只看盘上的就绪记录,不跑测试

就绪记录:~/.dsh/subagent-route-pin.ready.json

{
  "schema": 1,
  "plugin": "subagent-route-pin",
  "version": "0.8.13",
  "at": "2026-10-20T12:04:11.208Z",
  "mounted": true,
  "providerName": "pinned-account",
  "baseProvider": "spawn",
  "route": "trae/deepseek-v4.1-flash",
  "routeSource": "config row",
  "configRow": "trae/deepseek-v4.1-flash",
  "settingsDefault": "xiaomi/mimo-v2.6-flash",
  "settingsDefaultShadowed": true,
  "experts": 0,
  "fallback": true,
  "teamInjection": true,
  "capabilities": { "teamInjection": "installed", "enabled": true, "seams": ["startActivation"] }
}

route / routeSource 是当前真正生效的路由。优先级是 experts[专家名] → 配置行(设置页写的那个)→ 设置文件的 default;配置行一旦有值,~/.dsh/subagent-route-pin.json 里的 default 就会被盖住,记录里用 settingsDefaultShadowed: true 明确标出来,同时打一条 WARN——否则「我改了 JSON 但模型没变」在外部完全查不出原因。

test/logs.mjs 校验:插件身份、版本与本地 package.json 一致、是否挂载、能力标志、实际挂住的接缝(seams 里没有 startActivation 会 WARN:那说明这个构建可能根本不调被包住的方法)、回退开关、providerName -> baseProvider、生效路由与来源(settingsDefaultShadowed 为真时 WARN 点名被盖住的那个值)、记录龄(超过 7 天提示重启 DSH)。--strict 时 teamInjection: 'unavailable' 直接判失败——发版前该用这个。

有控制台输出时还能一起验那五行:

node test/logs.mjs --log captured-console.txt

默认 provider delegates / hook installed / capabilities published 三条必需,team member label= 在还没委派过时记为 none。

失败回退

fallback: true(默认)时,钉住的这条路由起不来不会直接砸掉委派:

场景行为日志
单发子代理,pinned-account 的 start() 报错去掉注入的路由,用调用方本来的路由重试一次pinned route … failed to start (…); retrying on the inherited route …
团队成员,创建调用报错把 request.agentOptions 还原成调用方原本要的那份(Agent Teams 自己的成员路由),重试一次team member … failed to start on …; retrying on the requested route …
团队成员本来没带路由(被插件填的)删掉注入的 agentOptions,用继承路由重试一次…; retrying on the inherited route
重试后仍然失败抛出原始错误,不吞—
注入的路由和继承路由本来就一样不重试(重试等于再撞一次同一额度)—

边界要说清楚:只在启动阶段可观测。子代理跑起来之后,流式请求中途的 429/额度错误发生在 start() 返回之后,插件抓不到——那种情况要看 DSH 自己的重试与错误上报。所以 fallback 兜的是「开局就撞墙」,不是「跑到一半断粮」。

兼容性

兼容矩阵

DSH / @deepseek-ai/dsh-tool-subagent状态说明
0.2.0-rc.1 ≤ 版本 < 0.4.0✅ 覆盖范围当前 peerDependencies 写的范围,0.2.1-rc.2、0.2.1-rc.3 实测 139 项断言全绿;本机构建只有 startActivation(无 startContinuable),插件按 startActivation 注入
0.2.1-rc.3✅ 已验证桌面端 0.2.1-rc.3 上 readiness 记录显示 seams: ["startActivation"]、teamInjection: installed,配置行路由生效
0.2.0-rc.x❓ 未验证接口面看起来一致(旧版走 startContinuable),但没跑过;装了会看到 peer 警告
>= 0.4.0⚠️ 未验证超出 peer 范围,DSH 插件管理器会标「异常」并拒绝启动;上游一旦改动 provider 或创建子代理的接缝,日志会先缺,插件按能力标志降级

peerDependencies 从「钉死单个 0.2.1-rc.2」放宽到 >=0.2.0-rc.1 <0.4.0:DSH 每次发 rc 都不再让这个插件被判不兼容、也不会因此被插件管理器拦下。范围上限留在 0.4.0,是因为 provider 和创建子代理的接缝属于内部契约,跨 minor 需要重新验证——发版前请跑 npm test(139 项断言)与 npm run ready(真实宿主上的 readiness)。

同一个范围还写在 package.json 的 engines.dsh 里,两处必须逐字相同(npm test 会比对,改一处不改另一处会红):

位置谁在读读来干什么
peerDependenciesDSH 插件管理器、npm装不装得下;超出范围标「异常」并拒绝启动
engines.dsh插件市场的索引构建(probe-npm)、Discover 页的筛选在列表里按「我这个 DSH 版本能不能用」过滤,是装之前的第一个信号

两份范围漂移是静默的:包照样列在市场上,只是筛不到、或装到一半才报不兼容。所以它是一条断言,不是一句注释。

接口依赖

依赖说明
ctx.subagents.registerProvider缺失时置能力标志 teamInjection: 'unavailable' 并告警,不阻断 profile 启动
ctx.subagents.startActivation / startContinuable至少一条存在且可被定义 own property;两条都在时优先 startActivation(当前构建的原生委派与 Agent Teams 都走它),两条都不在则置 unavailable 并退化为「仅单发子代理可控」
@deepseek-ai/dsh-tool-subagent 的 provider 接口能力表必须声明 agentOptions;内置 spawn 自带
浏览器侧 configForms / slots / locale / remote.session.modelCatalog页面用;缺失时卡片显示「不可用」状态
设置行 patch 语义整行替换 config,不是合并

读取能力标志(宿主侧):

import { teamInjectionCapabilities } from 'dsh-subagent-route-pin'

// { teamInjection: 'installed' | 'unavailable', enabled?: boolean, seams: string[], reason?: string }
const capability = teamInjectionCapabilities(ctx.subagents)
if (capability.teamInjection === 'unavailable') {
  // 团队成员会保持父路由 —— 这里决定要不要降级/提示用户
}
// seams 是实际包住的方法名:['startActivation'] 表示当前构建真的会走到它

同一份结果也挂在 ctx.subagents[Symbol.for('dsh-subagent-route-pin.team-hook')].capabilities 上;0.8 之前的 …continuable-hook 符号仍然读写,旧调用方不用改。

teamInjection 依赖接缝方法可被定义 own property;若 DSH 后续冻结该服务对象,插件置 unavailable 并退化为「仅单发子代理可控」。

测试

node test/run.mjs        # 139 项断言,不需要 DSH 在跑
node test/logs.mjs       # 检查真实启动留下的就绪记录
npm run verify           # 两个一起,且 --strict

139 项断言:路由优先级、文件热读、strict 校验、团队注入、覆盖团队成员自带路由(默认覆盖 / overrideMemberRoute: false 时尊重 / 保留调用方其他字段 / 丢弃旧路由的 effort / 覆盖失败退回调用方原路由)、开关关闭、缺路由不阻断、dispose 还原、重复挂载换路由、能力标志(installed / unavailable / 无任何接缝)、多接缝(只有 startActivation / 只有 startContinuable / 两条都在时优先前者且只注入一次)、启动失败回退(单发 / 团队 / 关闭开关 / 重试仍失败 / 同路由不重试)、日志契约、就绪记录(正常挂载 / 缺服务 / 版本一致 / 生效路由与来源 / 设置文件 default 被配置行盖住时报警)、发包元数据(engines.dsh 与 peer 范围一致 / bin 路径无前导 ./ 且文件存在 / 包内 cordis.patch.yml 的 name 指向本包且是可解析的模块路径)。

test/run.mjs 证明的是插件逻辑没坏;test/logs.mjs 证明的是它在这个 DSH 构建里还挂着。两件事会分别失败,上游接口变更先体现在后者。

升级回归

升级 DSH 后按这个顺序走,别跳步:

node test/run.mjs                 # 1. 逻辑层:128 项还绿吗
# 2. 重启 DSH(只改插件不用,改 DSH 本体必须)
npm run ready                     # 3. 挂载层:就绪记录还是这次启动写的吗

第 3 步读到 capabilities.teamInjection: "unavailable" 就是上游把创建子代理的接缝动了——此时单发子代理仍可控,团队模式成员退回父路由。读到 seams 里没有 startActivation,说明钩子包住的方法当前构建不调用,同样是接口变了。把 peerDependencies 和兼容矩阵一起对一遍,再决定是适配还是回滚。

发布

包名是不带 scope 的 dsh-subagent-route-pin(npm 上确认未被占用),因此不需要拥有任何 npm 组织/scope,登录任意账号即可发布:

npm login --registry=https://registry.npmjs.org/
npm publish --registry=https://registry.npmjs.org/   # 无 scope,默认为 public
pnpm pack --pack-destination dist

两个源必须显式指定。国内机器通常把全局 registry 指向 registry.npmmirror.com,那是个只读镜像:npm login 会把你带到 CNPM 的注册页并报 Public registration is not allowed,而 npm publish 打到它上面直接 401。本机 npm config get registry 返回的就是 https://registry.npmmirror.com,所以上面每条命令都带 --registry。想省掉参数就在项目根目录建一个只含 registry=https://registry.npmjs.org/ 的 .npmrc(项目级配置优先于全局),并把它加进 .gitignore。

发布前先跑 npm publish --dry-run --registry=https://registry.npmjs.org/,确认 Publishing to https://registry.npmjs.org/ 且没有任何 npm warn——bin 路径写成 ./bin/x.mjs 这类问题只在 warn 里出现,publish 照样 exit 0,发出去的包里却没有命令。

若以后要改到自己的 scope(例如 @yourname/dsh-subagent-route-pin),必须同时改这 4 处,少一处就会出现「装上了但不加载」:

#位置作用
1package.json → name安装名、依赖解析名
2cordis.patch.yml → 行的 nameprofile 加载器按包名找入口
3client.js → __ModuleLoader__.load({ id })浏览器端模块 id
4client.js → CONFIG_NS_CANDIDATES设置页绑定配置行的候选名

另需同步 test/client.mjs、test/browser-harness.html 里的 id 断言,以及 README 里的安装示例。

GitHub 仓库名与 npm 包名保持一致(dsh-subagent-route-pin),仓库归属 tkaiz0406 是 GitHub 账号,与 npm scope 无关,repository/homepage/bugs 无需随 npm 改名。

回滚

dsh plugin --profile desktop remove "dsh-subagent-route-pin"

或删掉 profile cordis.patch.yml 里 id: subagent-route-pin 那一行。

License

MIT — 见 LICENSE。

Ähnliche Plugins