dsh-dev-crew
A DeepSeek Harness plugin that mounts one subagent delegation tool per configured role and model, each bound to its own model provider route.
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:ivanon/dsh-dev-crew说明文档
阅读完整 README ↗配置
在 profile 的 cordis.patch.yml 中配置角色。每个角色可绑定一个或多个模型,
每个模型对应一个独立的委派工具。
- id: dsh-dev-crew
config:
roles:
- id: implementer
enabled: true
models:
- alias: default
provider: kimi-coding
model: k3
- id: reviewer
enabled: true
models:
- alias: ds
provider: deepseek-official
model: deepseek-v4-flash
- alias: kimi
provider: kimi-coding
model: k3
工具名规则:单模型角色为 subagent_,多模型角色为
subagent__。上面的配置会挂出 subagent_implementer、
subagent_reviewer_ds、subagent_reviewer_kimi 三个工具。
provider 必须是 Models 设置页中已就绪的路由。未配置或不存在的路由不会挂载
工具;插件会通过 ctx.logger 记录一行说明原因的警告,但该警告在当前 headless
一次性执行模式下不会打印到终端(见「已知限制」)。
内置三个角色 implementer / reviewer / researcher,各自带有写好的 persona
与工具范围,默认全部停用 —— 因为路由取决于你自己配置了哪些 provider。
用这三个 id 时只需写 id + models + enabled:未提供的 persona 与
toolFilter 由插件按 id 用内置模板填充。显式写出则以你的值为准,且是整体替换
而非与内置值合并 —— 自己写 toolFilter 就要把想保留的 deny 项一并列出。非内
置 id 不做填充。
无论是否填充,每个角色的子代理都看不到本插件挂出的其他委派工具:插件会把本次实
际挂上的其他 subagent_* 工具名追加进该角色的 deny,避免子代理沿角色链继续
向下分派。
两个角色用同一个 id(或同一角色内两个模型用同一个 alias)会推出重名工具,插
件在挂载前直接报错,不会挂上一个卸不掉的实例。
配置支持热更新:宿主组合了 settings 服务时,本插件会在 dsh-dev-crew
命名空间下注册用户设置文档,roles 等字段的变更会即时同步到已挂载的工具集,
无需重新加载插件。未组合 settings 服务的部署(例如 headless)不受影响,继续
使用 profile 里的入口配置。
gate.enabled 是例外:它只在插件挂载时决定是否注册纪律 guard,中途通过设置
页开关不会生效,需要重新加载插件才能应用。gate.plansDir 不受此限制,热更新
后立即生效。
HTTP API 与配置界面
宿主组合了 @deepseek-ai/dsh-host-webserver(即 ctx.get('webServer') 非空,通常
是 Web host,headless 部署没有这一层)时,插件在 /crew/api 前缀下注册三条路由:
| 路由 | 方法 | 说明 |
|---|---|---|
/crew/api/health | GET | 返回 { mounted, skipped }:已挂载的委派工具名与被跳过的路由及原因,补上 headless 下 logger.warn 不可见的可观测性缺口 |
/crew/api/settings.get | POST | 返回 { config, revision }:脱敏后的当前配置与 revision |
/crew/api/settings.update | POST | body 为 { config, expectedRevision },走 settings.update() 的 merge-then-validate;revision 冲突返回 409 REVISION_CONFLICT |
三条路由都要求 Host 头精确匹配回环地址(localhost / 127.0.0.1 / [::1] /
::1)或部署显式配置的 trustedHosts(当前插件把它硬编码为空数组,尚无 schema
与界面绑定),不匹配一律 403 UNTRUSTED_HOST。这只是主机名白名单,不是 CSRF
防护:Host 头检查挡不住浏览器页面向 localhost 发起的跨站 POST,当前定位是
「仅面向本地信任环境」,请勿在暴露给不受信任网络的部署上依赖它。
配套的客户端配置界面(src/client/CrewSection.tsx,通过 dsh.client.inject 挂进
settings.section)走同一套 HTTP API 读写配置、展示角色列表与健康状态。
这两条能力都需要 Web host:在 headless 部署下(例如本仓库用于验收的
crewtest profile),组合树里没有 webServer 服务,三条路由与配置界面都不存在
——这是 headless 部署形态本身的限制,不是 bug。
在组合了 Web host 的部署下,路由注册走的是 ctx.inject(['webServer'], hctx => { hctx.effect(() => registerCrewApi(hctx, {...})) })(registerCrewSettings 对
settings 服务同理):ctx.inject 会新建一个只在 webServer 服务就绪后才执行
的子插件,服务缺失或还没轮到时子插件停在 PENDING、主插件不受影响,服务就绪后
自动执行——不依赖 webServer/settings 是否已经在 apply() 执行的那一刻存在。
webServer/settings 都没有写进主插件顶层的 inject 数组,因为那样会让
整个插件在 headless(不组合这两个服务)下永久 PENDING。
已在新建的 crewtestweb profile(['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', 'dsh-dev-crew'])上端到端实测通过,包括
settings.update 的写入 + revision 递增 + 回读一致:
$ curl -s localhost:3099/crew/api/health
{"ok":true,"value":{"mounted":["subagent_implementer"],"skipped":[]}}
$ curl -s -X POST localhost:3099/crew/api/settings.get
{"ok":true,"value":{"config":{...},"revision":0}}
$ curl -s -H 'Host: evil.com' localhost:3099/crew/api/health
{"ok":false,"error":{"code":"UNTRUSTED_HOST","message":"request rejected by the plugin trust fence"}}
(第三条正确返回 403。)连续重启该 profile 三次复测,三条路由每次都正确注册, 排除了偶发时序窗口的可能。
客户端配置界面(CrewSection.tsx)的实际浏览器渲染(三态健康显示、保存失败时
表单不丢内容等)未做人工可视化验证,只验证了它依赖的 HTTP API 契约;见「已知
限制」。