架构说明
August 23, 2026 · View on GitHub
决策记录:合并自 monitor 与 director 的设计取舍。
约定:被后续变更取代的决策保留原文,在小节标题后标注(已被 x.x 取代,YYYY-MM-DD),新决策写入对应小节。
1. 概览
dsh-subagent-pro 是 DSH Web 的常驻扩展插件,单 bundle entry 同时提供三件事:
- 实时子代理监控:监听 subagent/start / subagent/end 全局事件,归因到根会话,浏览器浮层面板轮询 /api/dsh-subagent-pro/snapshot;
- 角色路由委派:注册 subagent_role 模型工具,四层回退解析 provider/model/persona/toolFilter;
- Claude Code 风格 agent md 注入:扫描 ~/.dsh/agents/.md 与
/.dsh/agents/ .md,合并到角色表,正文作为 persona 注入。
数据流:
user -> main agent -> subagent_role tool -> resolveRoute -> SubagentStartRequest{persona, agentOptions, toolFilter} | ctx.subagents.start(provider, request) | child session with persona
事件流:
subagent/start -> ctx.on('global') -> parent-chain walk -> rootId -> runs Map -> /api/dsh-subagent-pro/snapshot (every 1s)
2. 设计决策与理由
2.1 为什么合并 monitor + director 而不是独立两个插件
- 两者都是常驻扩展(页面刷新后状态保留),职责重叠:director 的 subagent_role 工具返回的子代理正是 monitor 关心的对象;
- 合并后只需一个 cordis entry,少一个 bundle patch、少一处 settings 命名空间管理;
- 客户端 UI 共享一个 toggle 按钮和共享 store(useSyncExternalStore),无需 prop drilling。
已选方案 A:单插件 dsh-subagent-pro。
2.2 为什么 toggle 按钮放在 conversation.input.left 而不是 sidebar
HUD 插件(a903067276-rgb/dsh-hud)使用 conversation.input.left slot 放置状态按钮;DSH 官方 composer chrome 也用同位置放附件按钮。这是一个与对话强相关的语义位:
- 监控面板的关注对象就是我(主代理)派出的子代理,属于对话场景;
- sidebar footer 偏向全程序控制项(设置、主题、用户),子代理监控过重。
monitor 原版的 sidebar 文字按钮已替换为 HUD 风格的 28x28 SVG 图标按钮(线性 SVG 子代理树状图标 + warn-yellow 角标 + .is-open 激活态),token 与官方对齐:--dsw-alias-state-warn-tertiary、--dsw-alias-state-warn-primary、--dsw-alias-state-warn-label。
2.3 事件为什么走 { global: true } 而不是加入 subagent 父作用域
同 monitor ARCH 2.3:事件按委托方(父会话)作用域分发;本插件挂在根组合上,必须 global 监听才能收到所有事件,再走父链归因到根会话。
2.4 事件为什么合并 listDescendants(history fill)
同 monitor ARCH 2.4:刷新 / 重启后事件仓库清空,但子代理记录在持久目录里。合并 catalog 提供免费的 label/mode/depth 字段 + 历史回填。
2.5 角色优先级为什么是 project md > global md > settings
- settings.roles 是用户在设置面板里手写的调试沙盒,临时实验友好;
- agent md 是项目级 / 全局级的代码化部署形态,可纳入版本控制、跨机器同步。
设计取舍:md 是部署形态(immutable + 版本化),settings 是 UI 实验形态(mutable + 即时生效)。当两者冲突时,md 赢——用户把 md 写进 git 时就知道自己在做什么;settings 是临时覆盖层。
非冲突的情况下三者并存,主代理指引全部列出,UI 把 md 角色显示为只读、把 settings 角色显示为可编辑。
2.6 为什么 agent md 正文直接作为 persona 注入
director 已验证:role.persona 通过 buildSubagentRequest({ persona: route.persona }) 进入 SubagentStartRequest.persona,到达子代理的 system prompt。这是 agent md -> 子代理行为的唯一注入路径,与 settings 角色完全一致。
frontmatter 的 schema 设计:
字段 | 必需 | 映射到 | 说明 name | 否 | displayName | 缺省取文件名 description | 是 | description | 缺省回退文件名 + warn tools | 否 | toolFilter.allow | 空格分隔 model | 否 | provider / model | provider/model 拆分;仅 model 时 provider 继承 正文 | 是 | persona | 去掉 frontmatter 后整段作为 persona
2.7 为什么 settings 命名空间是 subagent-pro 而不是 subagent-director
director 拆 main + bridge 两个 entry 绕开 webServer inject 闭包(详见 director ARCH);本插件单 entry 直接 inject: webServer,所以不再需要 bridge,命名空间也无须兼容旧名。
迁移说明:旧 dsh-plugin-subagent-director 用户需要把 settings.yaml 里的 subagent-director 块改名为 subagent-pro。CHANGELOG 标注。
2.8 为什么 4 层回退解析而不是显式 provider/model
4 层回退是 LLM route 解析的自然形态:
- 模型在调用时显式给出(per-call override)
- 角色模板绑定(per-role template)
- 插件全局默认(deploy-wide policy)
- 不注入,让 default-route seam 继承父模型(zero intrusion)
director ARCH 6 的论证、unittests 19 个全过。
2.9 为什么 persona 注入要 capability-check
只有部分 subagent transport provider 支持 persona / toolFilter(例如 spawn 支持,fork 不支持);不支持时通过 assertDelegationCapabilities 抛 subagent-director: role binds a persona but transport provider ... does not support the persona capability,避免运行时静默丢弃 persona。
2.10 为什么增加 subagent_providers 工具而不是复用其它工具
主代理在很多场景下需要先知道「现在能用什么 provider / model」再决定调用 subagent_role。bridge-entry.ts 已经在 ctx.llm 之上做了 /api/dsh-subagent-pro/llm/{providers,models,reasoning-efforts} 三个 HTTP 端点供 UI 用,但模型本身没接口。直接暴露同一个 ctx.llm 表面给 agent 是最小成本方案:
- 数据源唯一:与设置面板下拉同源,新增 / 删除 provider 不需要改 model 端 schema;
- 注册简单:只依赖
ctx.llm,不依赖 subagent transport provider,所以放在roles.ts的 apply-time 立即注册,无需subagent/provider-added事件; - 缺服务容错:
llm不可用时返回空数组而不是抛错,符合「工具失败不应打断对话」的稳健性预期; - 输出 schema 用
oneOf三分支(providers / models / reasoning),让模型在工具调用后能精确区分。
2.11 为什么 lib/ 不进版本库(分发策略偏离 monitor)
monitor 沿用 release-copy(lib/ 提交进 git,安装零构建);本插件选择 prepare-hook 分发(T2 决策 2026-08-23,选项 2,见 #3):
- 源码是唯一真相:git 历史只含 src/,lib/ 由
prepare(npm run build)在安装时现场构建,避免"改了 src 忘 build、提交了过期 lib"的漂移问题; - 接受偏离的代价:
dsh plugin add link:./引用本地仓库时不触发 prepare(实测确认),因此 link 模式必须先本地pnpm build——已在 README 安装节明确提示;github:/ npm / tgz 安装由 prepare 自动构建; - 配套:
pnpm-workspace.yaml随仓库分发(allowBuilds: esbuild: true),保证 git 宿主依赖的内层安装能完成构建链(pnpm 11 的供应链门禁)。
3. 文件结构
src/index.ts # host 半主入口(apply) src/index-types.ts # 公共类型(SubagentProConfig) src/monitor.ts # 实时子代理监控(event attribution + snapshot 路由) src/roles.ts # 角色路由装配(delegation tool + default seam + guidance) src/agents-md.ts # agent md 加载器(Claude Code 风格 frontmatter 解析) src/route-resolver.ts # 纯函数 4 层回退解析 src/settings.ts # settings 命名空间 + 校验 + 实时快照 src/delegation-tool.ts # subagent_role 模型工具定义 src/llm-info-tool.ts # subagent_providers 模型工具定义(LLM 路由自省) src/default-route.ts # default route seam(包装 start/startContinuable) src/guidance.ts # system prompt 角色清单 section src/client/index.ts # client 半主入口(slots) src/client/styles.ts # 全部 CSS(inlined) src/client/store.ts # 页面级共享 store(useSyncExternalStore) src/client/toggle.tsx # HUD 风格图标按钮(28x28 SVG + warn-yellow 角标) src/client/panel.tsx # 浮层面板(状态点 + 拖动 + 调高 + 收起) src/client/role-editor.tsx # 设置面板角色编辑器(默认值 + settings 角色 CRUD)
4. 测试覆盖
src/tests/ 下的纯函数测试(tsx --test):
- route-resolver.test.ts:8 个用例(call/role/default/inherit 优先级、displayName 反查、role persona 注入、未知 role warn、reasoningEffort advisory、模型单独缺省)。
- agents-md.test.ts:10 个用例(kebab-case 校验、.md 后缀剥离、lastSegment 双分隔符、fileIdFromPath 组合、frontmatter 完整/缺失/不闭合、model 拆分、tools 拆分)。
- llm-info-tool.test.ts:15 个用例(id 兜底、validateLlmInfoArgs 三 action 校验、未知 action、getLlmInfoService 缺方法容错、createLlmInfoTool 三 action 分派、缺失 reasoning 块、缺 llm 服务返空、validation 早于 llm 调用)。
运行时集成测试依赖 DSH 真机;此处未自动化,建议在 DSH web profile 中手动验证:
- 点击输入区左侧的 toggle 按钮;面板在右上角出现。
- 在 ~/.dsh/agents/ 写一个 md,重启 DSH;settings -> Subagent Pro 出现该角色。
- 主代理在对话中调用 subagent_role({ role:
, prompt: ... });子代理启动后 persona 已注入 system prompt。
5. 版本与发布
沿用 monitor 的发布纪律(AGENTS.md §5):
- 版本号三处一致:package.json、CHANGELOG 最新条目、git tag;
- 双语 README 同 PR 同步;
- pnpm verify:docs 检查文档一致性;
- 决策记录 ARCHITECTURE.md 保留旧决策,新决策写新小节。