dsh-preset-agent-manager
DSH(DeepSeek Harness)插件:预设智能体管理器。
它把「预设智能体」做成 DSH 里的一等公民:每个预设智能体有自己的 id、名称、触发域描述、persona,以及一份严格插件/工具白名单。主智能体可以在对话中自主调用 preset_agent_dispatch 把任务派给它,子会话在独立会话中运行、只拿到白名单内的工具、无法再次委派、结束后销毁。
功能
| 能力 | 实现 |
|---|---|
| 侧边栏底部入口 + 全屏管理面板 | 浏览器半注册在 sidebar.footer.action 与 shell.overlay |
| 创建 / 编辑 / 删除预设智能体 | 面板;删除需二次确认并显示当前活跃子会话数 |
| 插件白名单 | 从当前 profile 的 Loader 行与各 preset 组合行中勾选,逐行显示启用状态、fiber 阶段、不可用原因 |
| 工具白名单(严格生效) | 勾选后写入 toolWhitelist,运行时先作为子会话的 toolFilter.allow,再在子会话作用域内加一道 tools.guard 兜底 |
| 模型工具 | preset_agent_dispatch(agent_id, task)、preset_agent_admin(action, …) |
| 持久化 | $DSH_HOME/data/dsh-preset-agent-manager/agents.json(原子写入 + 损坏文件自动旁置) |
目录结构
dsh-preset-agent-manager/
package.json # type: module;dsh.bundle.patch + dsh.client(浏览器行)
cordis.patch.yml # 本包作为 bundle 时的插入行(含 config.rev)
lib/index.js # host 入口:薄壳,按 config.rev 加载 impl.js
lib/impl.js # host 实现:注册表、两个模型工具、HTTP API
lib/client.js # 浏览器半:手写 bundle(window.__ModuleLoader__)
安装与激活
方式 A:标准 dsh plugin(需要 pnpm)
本包声明了 dsh.bundle.patch,所以 dsh plugin 会在安装成功后自动把本包加入 profile 的 dsh.profile.bundles:
dsh plugin --profile web add file:$env:USERPROFILE\.dsh\plugins\dsh-preset-agent-manager
dsh plugin是一个 pnpm 薄转发器。若提示pnpm not found on PATH,先corepack enable pnpm或npm i -g pnpm。dsh.profile.bundles只在 profile 启动时读取,这条路径需要重启 profile。
方式 B:不依赖 pnpm 的手工安装
# 1. 放入 profile 的解析路径(Node 从 profile 目录向上查找 node_modules)
$dst = "$env:DSH_HOME\profiles\web\node_modules\dsh-preset-agent-manager"
New-Item -ItemType Directory -Force $dst | Out-Null
Copy-Item "$env:USERPROFILE\.dsh\plugins\dsh-preset-agent-manager\*" $dst -Recurse -Force
# 2. 激活:把下面 4 行追加到 profile 的 patch 层
# (该 profile 声明了 patchReload: live,正常情况无需重启)
- insert:
- id: preset-agent-manager
name: 'dsh-preset-agent-manager'
config:
rev: '16'
卸载 / 停用
- 停用(保留数据与文件):删除 profile
cordis.patch.yml里的 insert 块。 - 卸载:移除
profiles/web/node_modules/dsh-preset-agent-manager与上述 insert 块;数据文件按需保留。
使用
- 侧边栏底部点击 ⚉ 预设智能体 打开面板。面板按视口自适应(
min(1440px, 100vw-40px)×min(940px, 100vh-40px)),窗口缩放时跟随;新建/编辑表单直接接管面板主体并按宽度自动分两栏或堆叠,不再是嵌套的小弹窗。 - 新建预设智能体:填 id / 名称 / 描述(触发域)/ persona;勾选插件白名单;勾选该智能体真正可以调用的工具。名称与描述就是主智能体判断「该不该派给它」的全部依据。
- 保存后主智能体即可在对话中自主委派,或由你用显式指令触发:
用日志追踪器排查 D:\logs\app.log 里的超时错误 - 主智能体侧调用形态:
{ "agent_id": "log-tracer", "task": "自包含的任务描述" }
返回:status(success / partial / failed)、output、tool_allow、warnings、error、startup_ms、duration_ms、session_id。
机制与保证
- 委派走 DSH 自己的 subagent seam:
ctx.subagents.start(provider, request),provider 取spawn(当前 profile 同时注册spawn与fork)。子会话是普通子 Agent,拥有自己的 session 与 scope。 - 禁止嵌套:
maxDepth: 1(父 depth 0,子 depth 1,再委派即被SubagentDepthError拒绝)。白名单与作用域 guard 是第二、三重保证——见下。 - 严格白名单语义(两层,均经实测确认):
- 继承层掩码 —
toolFilter.allow交给 DSH 的tools.restrict(),它过滤该 scope 继承的一切(全局层 + 全部祖先层),只豁免该 scope 自己注册的工具。于是被勾选的工具可用,未勾选的(包括全部 agent preset 行注册的工具)在子会话的 prompt 中不存在,调用也会被拒绝。 - 自身层 guard — 第 1 层按设计豁免了子会话自身作用域注册的工具,而委派工具(
subagent、subagent_fork)恰好属于这一类:实测中即使白名单为空,子会话仍能看到subagent。因此主机半在子会话发布后,通过child.ctx.tools.guard(...)在其作用域内再注册一道单调 guard:不在白名单内的调用一律返回tool "<name>" is outside preset agent "<id>" plugin whitelist。实测子会话原文回执该错误,并自报tools=0。run_code(PTC 传输而非能力)被显式放行。
toolFilter.allow只接受调用方作用域继承集里的名字(该集合≠进程全局视图:host 插件行把工具注册在自己的 scope 里)。因此主机半用parent.ctx.tools.schemas()解析白名单,无法解析的名字在委派前剔除并写入warnings,不会让委派抛错。- 两层都在子会话自己的作用域内,随子会话销毁;guard 在子会话发布后立即注册,早于模型第一次工具调用(模型首轮往返)数十倍的时间裕度。
- 继承层掩码 —
- 空白名单是合法配置:不勾任何工具的预设智能体(纯推理/写作型)正常运行且返回
success;只有白名单丢失了名字才会降级为partial并附警告。 - 隔离与降级:每个预设智能体在自己的子会话中组装环境,会话结束即销毁;白名单内插件初始化失败不会中断主会话,子会话会带着警告继续,
status降级为partial。 - persona:作为 per-child persona 注册在子会话 scope 上,只覆盖该子会话,不影响父会话与兄弟会话。
- 并发:
preset_agent_dispatch声明isConcurrencySafe: () => true,同一预设智能体的多次委派各自独立运行、互不干扰。 - 持久化:
agents.json采用「临时文件 + rename」原子写入,写入串行化;损坏的 JSON 会被旁置为agents.json.corrupt-<ts>而不是被覆盖。
数据文件
{
"version": 1,
"agents": [
{
"id": "log-tracer",
"name": "日志追踪器",
"description": "排查日志、慢查询、错误堆栈",
"persona": "你是一个专业的日志分析专家……",
"pluginWhitelist": ["@deepseek-ai/dsh-tool-fs"],
"toolWhitelist": ["read", "grep"],
"createdAt": "2026-09-15T10:00:00.000Z",
"updatedAt": "2026-09-15T10:00:00.000Z"
}
]
}
toolWhitelist 是本插件对参考结构的扩展字段:它是真正生效的严格白名单,pluginWhitelist 记录人在面板上的选择意图并驱动不可用插件的告警。
HTTP 接口(浏览器半使用)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /preset-agents-api/state |
注册表 + Loader/preset 插件清单 + 工具清单 + provider + 诊断 |
| GET | /preset-agents-api/plugins |
仅插件清单 |
| POST | /preset-agents-api/agents |
新建或更新一个预设智能体 |
| POST | /preset-agents-api/agents/delete |
删除(返回 runningChildren) |
路由由 ctx.effect() 绑定到插件 fiber,卸载或更新插件行时随 fiber 一并移除。
热更新(config.rev)
lib/index.js 是按 config.rev 加载 lib/impl.js 的薄壳:改动 impl.js 后把 patch 里的 rev 递增,Loader 重新应用该行并让 Node 重新求值模块,无需重启 profile。同一个 rev 始终解析到同一个模块实例,行为与静态导入一致。
注意:若某次热更新导致插件 fiber 装载失败,profile 的 patch 监视器可能停止响应后续改动;此时重启 profile 即可恢复。
已知限制
- 没有 pnpm 就无法使用
dsh plugin add的裸包名形式(取决于环境是否安装 pnpm)。file:路径形式同样需要 pnpm;方式 B 完全绕开。 - 预设智能体不是 agent preset:它复用父会话的 preset 组合,靠
toolFilter+ guard +maxDepth做约束,因此不能授予某个 agent preset 行独有的工具。 - 面板的插件清单来自 Loader 与 preset 组合,
fiberPhase: 'failed'只有状态位,没有错误堆栈摘要(DSH 当前未提供 entry 级错误读取接口)。
License
MIT — 见 LICENSE。
No comments yet. Be the first to write one.