dsh-subagent-hub — DSH 子智能体枢纽
把一个你已经配好的模型发布成可 @、可观测、可评价的子智能体,直接长在当前 DSH 页面上。
子智能体不是「另一个模型写的一段回答」,而是真正的 DSH agent:它跑在同一套 agent 运行时上,
有自己的会话、工具、文件系统与沙箱,只是把 provider / model 换成你在配置页里指定的那一个。
长什么样
左侧是主对话,右侧是插件的悬浮球面板(已展开)。图是实际运行的截图,点开可看原尺寸。
评分排名表 —— 面板列出全部已配置的子 agent(各自的模型与工具策略),并显示谁在跑、哪个已归档:
实时输出详情 —— 点开某次运行即进入详情:逐 token 输出、tool 调用与产出文件,底部可确认该次运行的状态:
图里的 agent 名、模型 ID 与评测分数都是真实运行产生的数据。本插件不附带任何预设配置,第一次打开时列表是空的。
版本兼容性(先读这一节)
本插件大量使用 DSH 的内部接口 —— ctx.subagents.start、session/event 的 {global:true} 语义、
客户端 bundle 的装配约定等。这些都不是公开 API,而 DSH 本体还处在 0.x 阶段,内部结构随时可能变。
| 开发并验证过的版本 | @deepseek-ai/dsh@0.1.2-rc.1 |
| 声明兼容区间 | >=0.1.2-rc.1 <0.2.0(package.json 里写作 peerDependencies 的 ^0.1.2-rc.1) |
| 区间外怎么办 | 仍然加载,只警告。不会阻止你启动 |
区间外只警告、不阻止,是和插件的整体姿态一致的:它横跨 DSH 的多个子系统,
所以对每一项能力都用 ctx.get() 防御式取用 —— 取不到就关掉那项能力并说清楚关了什么,
而不是让整个插件起不来。版本不符时你会看到:
- 启动日志一条醒目的警告(含检测到的版本、来源、声明区间)
/sub-agent/api/health里的harness字段
curl.exe -sS http://127.0.0.1:3080/sub-agent/api/health
"harness": {
"detected": "0.1.2-rc.1", // 实际检测到的版本
"source": "dsh entry", // 从哪判出来的
"tested": "0.1.2-rc.1", // 本插件验证过的版本
"supported": ">=0.1.2-rc.1 <0.2.0",
"compatible": true, // true 兼容 / false 超出区间 / null 无法判定
"reason": null // 无法判定时说明原因
}
compatible 是三态的:true / false / null。null 表示判不出来——
DSH 没有把版本做成服务(已核实:没有 ctx.get('version'),pluginInventory 的 snapshot 也不带版本),
唯一可靠来源是磁盘上的 package.json。探测不到时它如实说「无法判定」并给出原因,
不猜、也不假设通过 —— 把「不知道」折叠成 true 是在编造结论。
如果你在
0.2.x或更高版本上使用,请先看/health的capabilities: 某项是false就说明那个能力被自动关掉了。这时插件的行为是降级并说明,不是崩溃。
安装
本插件未发布到 npm。 请按下面的方式从 GitHub 安装,
npm i dsh-subagent-hub这类命令不会成功。
从 GitHub 安装(需要本机有 git,pnpm 靠它取仓库):
dsh plugin --profile web add github:FlanceVoV/dsh-sub-agent
dsh plugin本质是 pnpm 的薄封装(它在 profile 目录里跑pnpm <args...>,再按装出来的实际包名 对账dsh.profile.bundles),所以 pnpm 认识的 spec 它都收:github:简写、完整 git URL、 tarball、link:/file:本地路径。等价的完整写法,以及锁定版本的写法:
dsh plugin --profile web add https://github.com/FlanceVoV/dsh-sub-agent.git dsh plugin --profile web add github:FlanceVoV/dsh-sub-agent#<tag 或 commit>注意上面的
#<tag 或 commit>是你自己要填的——本仓库目前没有打过 tag。 不写#…就是每次重装都拿最新的master,介意漂移的话请先打 tag 再用。
从本地目录安装(二次开发时用,改完代码热更新):
dsh plugin --profile web add link:<本插件目录的绝对路径>
装完必须重启一次 dsh web:新的 loader entry 与客户端 bundle 都只在启动时装配。
重启后确认它活着:
curl.exe -sS http://127.0.0.1:3080/sub-agent/api/health
返回 ok:true 且 initError 为 null 即正常。404 说明插件没被装配,
应检查 profile 的 dsh.profile.bundles 里有没有加上本插件。
顺手看一眼版本兼容性(见上一节):
curl.exe -sS http://127.0.0.1:3080/sub-agent/api/health
# 重点看 harness.compatible 与 capabilities 里有没有 false
卸载:
dsh plugin --profile web remove dsh-subagent-hub
# 然后重启 dsh web
怎么用
1. 打开开关
在输入框那一行的 @ 开关上启用。这是全局开关,打开一次对所有对话生效——
子 agent 的配置本来就是全局的(一张表、所有对话共用),开关的作用域必须和它一致。
开关状态走实时流:在任一窗口打开,其它窗口会立刻跟着变,不需要刷新页面。
2. 看悬浮球
启用后每个窗口右下角都会出现悬浮球:
- 收起:显示谁在跑 + 实时 tok/s
- 展开:列表(忙 / 闲、速率、最近结束)
- 点某一项:进入详情页,看是哪个会话、实时输入输出
tok/s 带 ~ 前缀表示这是估算值(流式进行中只能按字符数推算);
不带 ~ 的是权威值,由上一个已完成步骤的 token 用量算出。
3. 用 @ 委派
在消息里直接写 @名字,或者打 @ 从原生候选里选:
@研究员 查一下 X 的现状,给出可核对的结论
可以 @ 多个——主对话会为每个名字各发起一次委派。
忙的子 agent 不能被 @,这条规则在三个地方体现:候选菜单里直接不出现、
悬浮球一直列着谁忙谁闲、以及工具边界的硬拒绝。UI 只是礼貌,规则在工具边界。
4. 读评分
主对话拿到子 agent 的产出后会按四个维度打分(正确性 / 完整性 / 效率 / 成本), 分数进入排名表与跨轮回归表。回归表用于比较同一任务在不同轮次的表现, 所以同一个任务应当复用同一个任务标识。
配置子 agent
设置 →「子 agent」。同一个模型可以添加多次(例如同一个模型分别配「全工具」和「只读」两个角色)。
| 字段 | 说明 |
|---|---|
| 名称 | @ 的句柄,必须唯一(归档后重名的会被拒绝) |
| agent 提供商 | 子 agent 的传输实现;只有支持 agentOptions 的才会出现在这里 |
| 模型提供商 | 模型路由(已激活的、有模型清单的排在前面) |
| 模型 ID | 该路由下的模型;换提供商时会同时重新选模型 |
| 模型最大上下文 | 本插件的输入预算闸,见下 |
| 单次输出上限 | 传给模型的单次输出 token 上限 |
| 工具策略 | 继承父级(全工具)/ 只读(fail-closed 白名单)/ 无工具 |
| 推理档位 | 留空 = 用模型默认 |
| api 地址 | 留空 = 用 DSH 该路由的配置 |
| 凭据引用 | 留空 = 用该路由的默认凭据;只报「配没配」,不显示值 |
| 备注 | 可选 |
| 人格 / 系统提示词 | 可选,留空则沿用 DSH 的默认人格 |
归档不是删除:归档只把配置从 @ 候选里移走,历史运行与评价都还在,
设置页底部有「已归档」区块可以恢复。恢复时如果名字已被新配置占用会如实拒绝。
⚠ 「模型最大上下文」不是模型窗口。
AgentOptions只有{provider, model, reasoningEffort, maxTokens},没有上下文窗口字段, 窗口是模型目录的属性,无法按子 agent 单独调小。 所以这个字段被诚实地用作本插件的输入预算闸:预估超限就拒绝启动并说明原因, 而不会假装改了模型窗口。
运行护栏
设置 →「子 agent」→「运行护栏」。改动立刻生效并写进用户层配置文件。
| 键 | 默认 | 含义 | 能否热改 |
|---|---|---|---|
maxConcurrentRuns |
2 | 同时最多几个子 agent(真实费用护栏),超出排队而不是失败 | ✅ |
maxParallelPerSession |
3 | 同一父会话同时最多被 @ 几个 |
✅ |
runTimeoutMs |
900000 | 单次运行上限,到时取消并记为 timeout | ✅ |
outputTailChars |
20000 | 运行记录里保留的输出正文上限 | ✅ |
retentionDays |
0 | 运行记录保留天数,0 = 永久 | ✅ |
tokPerSecondWindowMs |
3000 | 速率采样窗口 | ✅ |
readonlyToolAllow |
[] = 内置默认 |
「只读」策略的白名单 | ✅ |
dbPath |
空 = $DSH_HOME/subagent-hub/subagent-hub.db |
sqlite 路径 | 需重启 |
logToStdout |
true | 是否把插件日志接到 stdout | 需重启 |
配置来源,后者覆盖前者:内置默认值 → cordis.patch.yml 的 config(部署层)→
$DSH_HOME/subagent-hub/config.json(用户层)。
写配置的姿态是刻意不对称的:读的宽容降级(未知键打 warning 并保留默认值, 配置写错不该让插件起不来),写的严格拒绝(写下去的东西会持久化, 静默纠正一个写错的值等于把错误固化)。
「需重启」的项在界面里显示为禁用并说明该改哪个文件,不会假装保存成功。
调大 maxConcurrentRuns 后已经排队的任务会立刻被放行——否则用户看到的会是「改了没用」。
护栏区块里显示的「排队中 N」就是让这件事看得见。
数据
$DSH_HOME/subagent-hub/subagent-hub.db(sqlite,用 Node 内置的 node:sqlite,无需额外依赖)。
表:agents / runs / rounds / evaluations / settings。
只存有界输出正文(outputTail,默认 2 万字符),更长的正文靠 session_id
回查 DSH 自己的会话日志——本插件刻意不复制对话正文,只存指针。
数据库带 application_id = 0x53554241('SUBA'):拿错文件会直接拒绝打开,
绝不往别人的库里写。
API
前缀 /sub-agent/api,是宿主与面板之间的唯一接口。
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /health |
存活、能力自检、DSH 版本与兼容性(harness) |
| GET | /state?sessionId= |
当前状态(含 agents 与 archivedAgents) |
| GET | /stream |
SSE 实时流(常开) |
| POST | /enable |
开关 |
| GET / PATCH | /config |
运行护栏 |
| POST | /model-detail |
查某个模型的推理档位等细节 |
| GET / POST | /agents |
列表 / 新建 |
| PATCH / DELETE | /agents/:id |
修改 / 归档 |
| POST | /agents/:id/restore |
从归档恢复 |
| GET | /runs |
运行记录列表 |
| POST | /run |
直接发起一次委派 |
| GET | /runs/:id、/runs/:id/detail |
运行摘要 / 详情(增量) |
| POST | /runs/:id/cancel |
取消 |
| GET | /runs/:id/evaluations |
该次运行的评价 |
| POST | /evaluate |
打分 |
| GET | /leaderboard、/regression |
排名 / 跨轮回归 |
| GET / POST | /rounds |
轮次 |
模型侧另有三个工具:subagent_run、subagent_evaluate、subagent_roster。
日常开发
node scripts/build-client.mjs # 把 lib/parts/*.js 拼成 lib/client.js
node scripts/build-client.mjs --check # 只校验「产物 == 分片」
node --test "tests/*.test.mjs" # 单元 + 集成(假 DSH 上下文跑通全链路)
node scripts/verify-client.mjs # 用真 React 加载并 SSR 渲染客户端 bundle
node scripts/dev-server.mjs # 真 HTTP + 真 sqlite 的离线宿主(127.0.0.1:8791)
node scripts/check-publish.mjs # 体检:扫绝对路径 / 用户名 / 密钥痕迹
node scripts/pack.mjs # 自检 → 打包 → 列出包内容
改宿主代码时优先用 dev-server 或集成测试验证,不要为了试一次就重启 DSH。
客户端源码的读写规则
客户端 bundle 必须是单文件(DSH 的客户端模块系统只暴露少量种子模块),
所以源码放在 lib/parts/*.js(按文件名顺序拼接),lib/client.js 是构建产物。
分片是唯一的真相来源。 契约测试会重算并比对,直接改产物会让测试立刻变红, 提示你「改分片,然后跑构建」。
lib/parts/*.js 单独看不构成合法 JS(第一片开 factory,最后一片才收尾),
所以对它们跑 node --check 会报错——这是预期,不是问题。同理,
改完 lib/parts/*.js 必须跑一次 build-client.mjs。
热更新边界
| 改了什么 | 生效方式 |
|---|---|
lib/parts/*.js + build-client.mjs |
热更新(不用刷新页面) |
lib/host.js、lib/src/*.js |
需要重启 dsh web |
cordis.patch.yml、package.json 的 dsh 字段 |
需要重启 dsh web |
排错
「dsh web 卡住了」
通常不是卡住。dsh web 是前台服务器,启动时打完那两行就不再输出,
在你按 Ctrl+C 之前它一直占着终端——「没有新输出」和「死住了」看起来一样。
按这个顺序判断它是活的还是死的:
# 1. 服务是不是真的在应答(这条最决定性)
curl.exe -sS http://127.0.0.1:3080/sub-agent/api/health
# 2. 每个客户端 bundle 是否都送得出去(都该是 200)
curl.exe -sS http://127.0.0.1:3080/plugins/events
# 3. 端口有没有被监听
Get-NetTCPConnection -LocalPort 3080 -State Listen
1 和 2 都正常就说明服务是好的,问题在终端或浏览器那一侧:
- Windows 控制台 QuickEdit:在窗口里点一下(比如为了选中文字去复制)会让控制台进入选择模式,
进程输出被暂停,看起来就是「卡死」。按
Esc或Enter退出即可。 根治办法:窗口标题栏右键 → 属性 → 取消勾选「快速编辑模式」。 - 浏览器页面本身:如果标签页是空白的,看它自己的控制台——服务端 bundle 都是 200 的话, 问题在页面内,不在宿主。
插件路由全部 404
先确认你挂在哪个 profile 上:
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | ForEach-Object { $_.CommandLine }
safemode 是救援 profile,设计目标就是零第三方插件——在那个 profile 下,
本插件与所有第三方插件的路由都会 404,/ 还会要求登录,那不是插件坏了。
回到正常工作状态就是重新跑 dsh web(即 web profile)。
「能打开,但有些功能是空的 / 不出现在界面上」
先查这两处,它们的顺序不能反:
curl.exe -sS http://127.0.0.1:3080/sub-agent/api/health
- 看
harness.compatible。 如果是false,说明 DSH 版本超出了已验证区间 (harness.detected是实际版本)——这是最可能的原因。 - 看
capabilities里哪一项是false。 每一项对应一个 DSH 服务:subagents/agents/tools/systemPrompt/llm/settings/credentials/sessions。 某一项为false就是某项能力被自动关掉了,而对应用户可见的症状是固定的:
| 能力 | 关掉之后你会看到 |
|---|---|
subagents |
@ 委派跑不起来,悬浮球永远没有任务 |
tools |
主对话看不到 subagent_run 等三个工具(面板仍能用) |
systemPrompt |
@ 的注入协议段没进系统提示词,主对话不知道该怎么处理 @ |
credentials |
配置页的凭据引用拿不到状态 |
llm / settings / sessions |
模型清单、设置读写、会话查询相应降级 |
harness.compatible 是 null 则表示无法判定版本(harness.reason 会说明原因)——
这时不要据此认为兼容,去看 capabilities 的实际结果。
关于终端日志
logToStdout(默认开)在宿主启动时会往 stdout 打一行本插件的信息。
它只打本插件自己的:cordis 的 exporter 是全局的,不过滤就会把所有插件的日志都灌进终端。
目录
lib/host.js 生命周期与装配(无领域逻辑)
lib/src/store.js sqlite:agents / runs / rounds / evaluations / settings
lib/src/discovery.js 自发现:路由、模型、窗口、传输、凭据(只问状态不问值)
lib/src/registry.js 校验与翻译:配置 → AgentOptions / ToolRestriction(纯逻辑)
lib/src/runtime.js 运行时:起子 agent、折遥测、守并发与超时
lib/src/tools.js 模型侧入口:三个工具 + 系统提示词协议段
lib/src/http.js JSON + SSE 接口层
lib/src/version.js DSH 版本探测与兼容性判定(零 DSH 依赖,只读 package.json)
lib/parts/*.js 客户端分片(唯一真相来源)
lib/client.js 客户端 bundle(构建产物,勿手改)
tests/ 单元 / 集成测试
scripts/ build-client / verify-client / dev-server / check-publish / pack
assets/screenshots/ README 用的界面截图(仅供展示,不参与构建与打包)
已知限制
- 「模型最大上下文」不是模型窗口,只是本插件的输入预算闸(见上)。界面上也这么写。
- 只保留有界输出正文:运行记录里存
outputTail(默认 2 万字符), 更长的正文靠session_id回查 DSH 的会话日志。这是刻意的,不复制权威副本。 - 改宿主代码需要重启
dsh web(DSH 的 HMR 不监听模块路径)。客户端改动是热更新的。 - 本插件走一次性运行,不支持续聊路径。
- 版本兼容性是已知的真实风险:本插件针对
@deepseek-ai/dsh@0.1.2-rc.1开发, 依赖的是 DSH 的内部接口。区间外的版本会加载但可能降级 —— 见「版本兼容性」一节, 以及/sub-agent/api/health的harness与capabilities字段。


No comments yet. Be the first to write one.