dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Turn a DeepSeek Harness (DSH) instance into a subagent any harness (Cursor / Claude Code / Codex / Gemini CLI ...) can call over MCP - plus a live monitor panel inside DSH Desktop.
将整个 DeepSeek Harness (DSH) 运行实例包装为可供 Cursor / Claude Code / Codex / Gemini CLI / Antigravity / Kiro / Qoder / VS Code (Copilot) / opencode 等调用方工具调用的子代理(subagent),并为 DSH Desktop 宿主界面提供实时状态监控悬浮卡片。
架构设计说明:使各类外部 Agent 工具能够将子任务分派给独立的本地 DSH 实例执行,而非仅在自身进程内运行轻量级函数调用。
系统由以下三部分组成:
| 模块 | 目录位置 | 技术说明 |
|---|---|---|
| MCP 桥接层 | bin/ + lib/ | 标准 Stdio MCP 服务器,对外暴露 dsh_task 等六个工具;每次分派拉起独立的 DSH 进程 |
DSH subagent profile | profile/ | 配置 DSH 进程以单次会话、无人值守、只做叶子节点模式运行 |
| GUI 宿主插件 | monitor/ + gui/ | 观察器把外部任务的实时状态灌进会话投影;悬浮卡片按调用方分组显示 |
| 文档路径 | 涵盖内容 |
|---|---|
| docs/install.md | 前置依赖、一键安装、五分钟验证、卸载、常见故障排查 |
| docs/configuration.md | 全部环境变量定义(全部 配置逐条来自源码)、profile 逐行说明、权限与叶子限制条件、卡片偏好设置 |
| docs/clients.md | 各客户端接入配置路径与 JSON 结构、手动接入方法、内置子代理路由规则 |
下文为系统架构设计、机制解析与实测记录(§1~§10)。
| 客户端工具 | 自动注册路径与键名 | 一键安装支持 |
|---|---|---|
Cursor(IDE + cursor-agent CLI) | ~/.cursor/mcp.json → mcpServers.dsh | ✅ |
| Claude Code | ~/.claude.json → mcpServers.dsh | ✅ |
| Claude Desktop | %APPDATA%\Claude\claude_desktop_config.json | ✅ |
| Codex CLI / Desktop | ~/.codex/config.toml → [mcp_servers.dsh] | ✅ |
| Gemini CLI | ~/.gemini/settings.json | ✅ |
| Antigravity | ~/.gemini/antigravity/mcp_config.json | ✅ |
| Kiro | ~/.kiro/settings/mcp.json | ✅ |
| Qoder | ~/.qoder/mcp.json | ✅ |
| VS Code / Copilot | %APPDATA%\Code\User\mcp.json → servers.dsh | ✅ |
| opencode | ~/.config/opencode/opencode.json → mcp.dsh | ✅ |
| Claude Code / Codex / Cursor 的内置 subagent | 写入全局指令使子代理任务转调 DSH | ✅ |
桥接层运行于 DSH 内部环境(包含一个 Profile 与两个宿主插件),上游接口变动将直接影响插件行为。以下为实测适配数据(基于从 app.asar 提取的实际运行源码验证,避免 app.asar.unpacked/ 中陈旧文件干扰):
| DSH 版本 | 兼容状态 | 说明 |
|---|---|---|
| 0.1.5-rc.1 | ✅ 已完成全量适配与验证 | 处理了 3 处破坏性接口变更(见下表) |
| 0.1.4 及更早版本 | ✅ 保持兼容 | 三处接口变更均包含向前兼容逻辑(见下) |
| 变更项 | 影响表现 | 适配方案 |
|---|---|---|
@deepseek-ai/dsh-llm 不再导出 assertNever(移至 dsh-util-values) | Profile 在插件树加载阶段抛出 SyntaxError,dsh_task 在 1.5 秒内失败且未生成结果文件、答复为空 | 移除该引用,在 runner 内实现 warnUnknownChunk() 对未知事件块记录告警并忽略,避免异常中断流程 |
permissionPresets.current() 参数由事件数组变更为 Session 对象 | 传入数组导致底层报 Cannot read properties of undefined (reading 'header') 且栈内全为内部调用帧 | 新增 currentPreset() 函数:优先按 Session 对象调用,失败时回退至事件数组模式,均失败才抛出原始异常 |
session.events 属性重命名为 session.log | 直接读取事件流返回 undefined | 新增 eventsOf(session) 兼容函数支持两种属性名 |
异常处理函数 fail() 现输出完整错误调用栈(此前仅输出 error.message),以便快速定位类似 presets.current() 参数签名变更的内部异常。
0.1.5-rc.1 发行版中附带的 lsp-stdio 与 tool-lsp 插件未同步更新 assertNever 引用路径,加载时抛出 does not provide an export named 'assertNever' 异常。由于插件树采用全量加载机制:
dsh --profile web 启动失败。
桌面宿主不受影响:desktop profile 默认未包含上述两项插件(经 --dump-config 确认日志中无 assertNever 报错)。
若需运行 web profile,可在配置中禁用对应插件:
- id: lsp-stdio
disabled: true
- id: tool-lsp
disabled: true
使用 --patch 传入包含上述两行 overlay 的配置即可正常启动。
dsh --version # 先看版本
node test/selftest.mjs # 协议级端到端:真拉 MCP + 真跑一轮(最能说明问题)
node test/launcher-heal-probe.mjs # dsh 入口脚本指向的入口文件还在不在(Desktop 换打包形态时第一个红)
node test/leaf-only-probe.mjs # row id 有没有被改名/删掉
node test/monitor-live-probe.mjs # 观察器 + 宿主判活
版本升级需重点验证两类项目:配置行 ID(row id)(共有 15 处 Patch 配置,变更会导致规则失配)与服务方法签名。测试脚本 test/leaf-only-probe.mjs 覆盖前者检查(已适配 0.1.5 起 tool-subagent-report(subagent-report)由加载行变更为协议内部消息 kind 的调整)。
dsh 子进程,并读写 $DSH_HOME 与本地工具配置文件。workspace-write(可读写目标工作空间,工作空间之外的文件操作由 DSH 沙箱拒绝)。需要全盘读写时在调用时显式传 permission: "danger-full-access",要更紧可传 "read-only";也可用环境变量 DSH_SUBAGENT_PERMISSION 改默认级别。run_code 调度工具,而不是一次一个原生工具调用。该选择由起进程时的 DSH_TOOLS_MODE=ptc 给出(lib/tasks.mjs 默认设置,可用 DSH_SUBAGENT_TOOLS_MODE=native 改回)。state/ 目录存放私有运行时数据,已在 .gitignore 中排除。日志、心跳与任务记录文件(task.json / meta.json / stderr.log)包含本地绝对路径、工作空间名、提示词与任务输出。仓库内不包含任何真实任务现场数据。.bak-dshsubagent-<时间戳> 或 <原文件>.bak-dshsubagent-<时间戳>),遇到无法解析的 JSON 文件自动跳过,不覆盖现有数据。taskkill 与 PowerShell 进程管理机制。本机各 Agent 工具通过 MCP 协议将任务委派给独立的 DeepSeek Harness 实例,并在指定工作空间内获取执行结果。
Cursor / Claude Code / Codex / … 的对话
│ ① 调用 MCP 工具 dsh_task(prompt, workspace, expected_seconds)
▼
dsh-subagent-mcp.mjs (MCP stdio server,本目录 bin/)
│ ② 分配 job id,把提示词从 stdin 交给子进程
▼
"DSH Desktop.exe" --profile subagent ← 一台完整的 DSH 实例,一轮会话
│ ③ 在 workspace 里真干活:读写文件、跑命令、跑测试、联网检索
▼
$DSH_HOME/sessions/<workspace>/session-<uuid>/ (会话落盘,可在 DSH GUI 里复查)
│ ④ 最终答复 → result.txt / stdout
▼
dsh_task 返回 "status: ok" + DSH 的原文
与内置子代理的主要区别:任务作为独立的 DSH 系统进程运行,拥有独立的上下文窗口、模型配置、工具链以及持久化会话记录,而非宿主工具内的函数调用。
# 桥接是否可用(等价于 harness 里的 dsh_health 工具)
dsh-subagent --where
# 真跑一轮:在指定工作空间里让 DSH 干一件可验证的事
dsh-subagent -w D:\some\repo "列出后端路由文件,汇总最近改动"
# 协议级自检(会真的拉起 MCP server 并跑一轮任务)
node $env:USERPROFILE\.dsh\subagent\test\selftest.mjs
# 等待规则:默认短超时自己等、只有 running 才轮询(会真的派两个任务)
node $env:USERPROFILE\.dsh\subagent\test\wait-policy-probe.mjs
# 监控窗口自动拉起(假 exe + 临时 DSH_HOME,**不会真的启动 GUI**)
node $env:USERPROFILE\.dsh\subagent\test\monitor-autostart-probe.mjs
# 台账残留记录(记录说在跑、pid 早没了)→ 必须按 pid 判活
node $env:USERPROFILE\.dsh\subagent\test\ledger-liveness-probe.mjs
# 叶子检查:外部任务不能再生子代理(配置级 + 解出会话日志看真实工具表)
node $env:USERPROFILE\.dsh\subagent\test\leaf-only-probe.mjs <sessionId片段>
# 卡片底部状态条的数字:真实任务日志 → token 用量(逐帧解 zstd,只读)
node $env:USERPROFILE\.dsh\subagent\test\usage-fold-probe.mjs --all
MCP 服务器名称统一为 dsh,在各客户端中工具名称形如 mcp__dsh__dsh_task(Claude Code)或 dsh.dsh_task(Codex / Cursor)。
| 工具名称 | 功能描述 |
|---|---|
dsh_task | 委托一项自包含任务。必须指定 expected_seconds 预估耗时;默认执行 45 秒短超时等待:窗口内完成直接带回结果(不需要轮询),超时未完成返回 status: running 与 job_id(见 §2.3) |
dsh_task_status | 查询任务状态或继续等待;返回 expected_seconds、deadlineAt、remainingSeconds、process_tree(实时后代进程数与整树 CPU 耗时)与 recent_activity(子代理此刻在干什么,包括 activity 事件);ok 时一并返回 DSH 的最终答复全文 |
dsh_task_cancel | 优雅取消任务:发送终止标记并通知子代理进程安全收尾退出 |
dsh_task_kill | 强制终止进程树:支持按 job_id 或按 caller 批量终止该调用方的全部任务;针对外部进程调用 taskkill 并校验退出码(失败时如实报告 killed:false 而非报告 killed: true);返回实际终止与已结束的任务清单 |
dsh_health | 健康探活:返回解析到的 dsh 启动器、DSH_HOME、默认工作空间、默认模型、每个调用方的并发数(activeByCaller)、每个实时任务的截止与进度(liveTasks)、最近任务列表,以及已接入模型列表(models:按 provider 分组,含 defaultModel、说明来源的 source(native 原生登记表 / cache 上次成功结果 / yaml 兜底解析)与枚举失败时的 error)和模型策略配置(modelPolicy.path / fileUrl / defaultModel / defaultReasoningEffort / presets) |
dsh_setup | 首次接入配置:将用户选定的默认模型写入策略文件(config/model-policy.md),只改写 默认模型: 声明行,保留文件其余内容与用户自定义修改。支持三种参数:default_model(必须为本实例已接入模型,裸 id 或 provider/id)、preset(如 本项目方案)或 policy_markdown(替换正文)。输入无效时直接报错(isError:true)并列出可用模型清单 |
dsh_task 参数详细定义| 参数名称 | 类型与约束 | 详细说明 |
|---|---|---|
prompt(必填) | 字符串 | 必须自包含完整任务上下文:DSH 独立进程无法读取外部调用方工具的对话历史或已打开文件,且执行过程中不会提问 |
expected_seconds(必填) | 正整数 | 调用方预估的任务执行秒数。缺失或非正数值将被直接拒绝,以 isError: true 形式返回错误结果。该参数同时决定截止时间:deadlineAt = startedAt + expected_seconds × DSH_SUBAGENT_DEADLINE_GRACE(默认宽限系数为 2),超时将终止进程树并记录 status: "deadline" |
acceptance | 可选字符串 | 单句验收标准描述文本(如“做完 = …”),记录至 task.json 并在结果中回显;超过 2000 字符将以 isError: true 拒绝 |
workspace | 可选字符串 | 目标工作空间的绝对路径;缺省时依次尝试外部调用方工作空间(MCP roots)与服务器当前工作目录(server cwd) |
wait_seconds | 可选数值(默认 45) | 本次调用最多阻塞等待的秒数(默认 45 秒短超时,推荐直接省略);传入 0 表示立即返回 job_id(不推荐,会拆解为多轮轮询,且不要传 wait_seconds: 0)。建议不要传递大于 60 的数值,多数 Agent 客户端单次工具超时阈值为 60 秒(见 §2.3) |
timeout_seconds | 可选数值(默认 1800) | 外层绝对超时时间(秒),作为第二重兜底保护,与 expected_seconds 截止机制同时生效 |
model / provider | 可选字符串 | 单次任务指定模型与服务商。模型清单动态取自 dsh_health 的 models.providers,权威来源是 DSH 自己的模型登记表(叶子 profile 用 --print-model-catalogue 读 llm.listProviders() / llm.listModels(),只枚举不推理;结果带缓存,探针起不来才退回解析 profile patch 与 settings.yaml,models.source 写明用的是哪一层)。规则配置见 config/model-policy.md。可调用 dsh_setup(preset: "本项目方案") 切换预设。省略 model 时先用策略文件里的 默认模型: + 默认推理强度:(发布默认 deepseek-flash / max);裸模型 id 若在多条 provider 路由下都有登记,由叶子进程按「该路由是否声明了这个推理档位」选路,路由级失败(余额 / 凭据 / 档位)自动换下一条;匹配不到已接入清单就交回 DSH 自己配置的默认模型;指定未接入模型时直接报错(返回 UNKNOWN_MODEL 或 NO_ADAPTER),不进行静默回退 |
reasoning_effort | 可选字符串 | 单次任务指定推理强度档位(必须为该模型在生效配置的 models[].reasoningEfforts 中声明的档位,常见包括 off/minimal/low/medium/high/xhigh/max)。传入未声明的档位时直接报错(返回 UNSUPPORTED_REASONING_EFFORT 快速失败,耗时约 0.5 秒且不调用模型;若该模型在别的路由下声明了这一档,叶子会先换路由重试)。结果头部回显 model: 与 reasoning_effort: 实际生效值 |
permission | 可选字符串(默认 workspace-write) | 权限模式:read-only、workspace-write、danger-full-access |
raw_prompt | 可选布尔值 | 为 true 时不附加默认的「委托说明」前缀,直接传递原始提示词 |
调用方标识识别:每个 MCP 连接在 initialize 请求中携带的 clientInfo.name 会记录至该连接创建的每个任务元数据中(task.json 的 caller 与 callerVersion,并在工具结果的 caller: 行显示)。未提供时默认为 "unknown",命令行直接调用记录为 "cli"。GUI 监控面板与 dsh_health 均基于此标识进行分组。
等待执行策略说明:dsh_task 默认等待 45 秒。若任务在此期间完成,结果直接在当前调用中返回,无需额外轮询;若超过 45 秒未完成,返回 status: running 与 job_id,此时外部 Agent 应在同一会话中调用 dsh_task_status(job_id, wait_seconds=30) 进行等待与轮询(每次等待 30 秒),直至达到终态(ok / error / deadline / stalled / cancelled / killed)。请勿直接传入 wait_seconds=0,避免过早拆分成多次模型轮询往返。
委托说明前缀:桥接层默认在提示词前添加简要委托指引(明确为单次会话、无人交互应答、结束时汇报修改文件与结果结论),防止 DSH 进程因等待用户交互而挂起。
操作约定已直接固化至 MCP 工具 Schema 定义中:
| 定义位置 | 规范内容 |
|---|---|
initialize.instructions | 包含 initialize 带委派操作手册 概要(≤1500字符):时间预估、验收标准、短超时优先等待、状态流转与失败排查说明 |
dsh_task.description | 包含 dsh_task 描述含完整委派约定(约 3250 字符):先拆再派的原则与阈值(一次只派一个可独立验收的产物、expected_seconds ≤ 300s;超过 300s、要改 3 个以上文件、或属于「先调研再实现再验证」的多阶段作业必须先拆;互不依赖的子任务同一轮里并发派出,有依赖的才串行)、禁止委派的清单(短指令作业自己做;dev server / watch / daemon / 交互式终端 / 需要成对收尾的独占资源不派 —— 收尾会连进程树一起带走,常驻的东西由调用方自己起后台作业)、明确的执行上限与经验区间(单文件 60deadlineAt = 开始时刻 + expected_seconds × grace、七种状态语义、基于 recent_activity / progress_bytes / last_progress_at 的排查流程、模型配置四步法与禁止项 |
各参数 description | expected_seconds 说明截止时间与参考区间;acceptance 提供可判定正例;wait_seconds 说明默认 45 秒短超时及 dsh_task_status(wait_seconds=30) 轮询规则(禁止传 0 或大于 60);permission 提示受限模式下使用 write/edit 工具;timeout_seconds/model/provider/reasoning_effort/raw_prompt/label 说明错误处理与错误代码(UNKNOWN_MODEL / NO_ADAPTER / UNSUPPORTED_REASONING_EFFORT) |
dsh_setup.description | 说明三种配置模式(default_model / preset / policy_markdown),强调仅修改 默认模型: 声明行并保留用户其余配置 |
dsh_task_status.description | 解析 progress_bytes、last_progress、recent_activity、process_tree、silent_seconds 字段定义 |
dsh_task_kill / dsh_task_cancel.description | 说明按 job_id 与按 caller 两种终止模式及超时止损场景 |
dsh_health.description | 说明 liveTasks、activeByCaller、看门狗阈值与 monitorHost 字段定义 |
自检套件包含对上述 Schema 文案的完整断言校验,确保规范描述不丢失。
当外部客户端取消当前轮次请求时,客户端会发送 MCP notifications/cancelled 通知。桥接层执行以下终止逻辑:
| 触发场景 | 桥接层处理逻辑 |
|---|---|
取消正在等待的 dsh_task 或 dsh_task_status 请求 | 精确终止该请求对应的单个子代理任务 |
| 收到未关联具体请求的取消通知 | 若当前连接仅有一个活跃任务在运行,则终止该任务 |
| 关闭 MCP 连接或关闭外部客户端 | 终止当前连接名下的所有活跃任务并退出服务进程 |
终止 MCP 服务进程(SIGTERM/SIGINT) | 终止本进程名下的全部任务后退出 |
服务不依据轮询空闲时长猜测连接状态,仅响应明确的停止信号。在 Windows 环境下通过 taskkill /F 或 TerminateProcess 强制终止外部进程,并通过测试确保取消通知到达后子进程实际退出。
长任务若默认采用 wait_seconds=0 会被拆解为多次频繁的 dsh_task_status 轮询请求,增加额外的模型推理开销。
桥接层采用短超时优先机制:dsh_task 默认阻塞等待 45 秒。任务在 45 秒内完成则直接返回结果,零轮询;超时未完成则返回 status: running,转由调用方使用 dsh_task_status(默认单次等待 30 秒)按轮次推进。
默认等待时长的评估依据:
| 客户端环境 | 单次工具调用超时阈值 | 设定结论 |
|---|---|---|
Codex(codex-mcp-client) | 60 秒(mcp_servers.<id>.tool_timeout_sec 默认值) | 作为最严格的限制条件 |
| Claude Code | Stdio 服务空闲窗口默认 30 分钟,单次上限约 28 小时;超过 2 分钟的调用自动转为后台任务 | 远宽于 45 秒 |
Cursor(cursor-vscode) | 官方未公开单次限制 | 参照 60 秒上限保守处理 |
依据 60 秒阈值预留 25% 安全余量确定为 45 秒,状态轮询设置为 30 秒。实测短任务在 DSH 侧耗时 1.4~1.7 秒,dsh_task 在 3.5 秒内即可完成并带回终态结果(通过 test/wait-policy-probe.mjs 验证)。
如需支持长任务单次直接返回,需同步调整客户端工具超时与桥接层等待配置:
# ① 先把调用方的单次工具超时调大(Codex 为例;command/args 用安装器写好的那两行)
[mcp_servers.dsh]
command = "C:\\Program Files\\nodejs\\node.exe"
args = ["C:\\Users\\<you>\\.dsh\\subagent\\bin\\dsh-subagent-mcp.mjs"]
tool_timeout_sec = 600
# ② 再同步把桥接层的等待调大(两个都可单独调)
DSH_SUBAGENT_WAIT_SECONDS=540 # dsh_task 的短超时
DSH_SUBAGENT_STATUS_WAIT_SECONDS=540 # dsh_task_status 的默认等待
相关配置通过环境变量覆盖,可通过 dsh_health 返回的 defaultWaitSeconds、statusWaitSeconds、waitModel 查看生效值。修改后需重启客户端生效。
桥接层动态获取模型清单,模型选择规则保存在外部策略文件中:
| 环节 | 技术实现与定义 |
|---|---|
| 已接入模型 | 通过 dsh_health 返回 models:{ providers:{ <你的路由名>:[…] }, defaultModel, source, error }(即 dsh_health.models)。主来源是 DSH 自己的登记表:叶子 profile 的 subagent-models.js 在 --print-model-catalogue 下只读 llm.listProviders() 与 llm.listModels()(不建会话、不发模型调用),结果规范化后按 provider 分组返回,source 为 native。探针暂时不可用时用磁盘缓存(source: cache,stale 标记新鲜度),缓存也没有才退回解析配置文件(source: yaml,探针失败原因在 modelCatalogueError 里;这条兜底按序合并 $DSH_HOME/profiles/subagent/cordis.patch.yml → $DSH_HOME/cordis.patch.yml → settings.yaml → settings.yaml.imported,只认 llm-pi-ai.providers.<route>.models 与 llm-deepseek.models 里的 id,绝不整文件扫 id:,一份都没认出时在 error 里点明搜过的路径)。注意 provider 配置本身是本机私有信息(含内网网关地址与密钥环境变量名),不进公开仓库:仓库的 profile/cordis.patch.yml 只留说明,真正的 llm-pi-ai 条目由 install.mjs 从本机已有配置里抽出并注入 $DSH_HOME/profiles/subagent/cordis.patch.yml 的 dsh-subagent-llm-providers 标记块(可用 DSH_SUBAGENT_PROVIDERS_FILE 或 $DSH_HOME/llm-providers.patch.yml 指定自己的片段) |
| 模型清单来源 | 见上表「已接入模型」:原生登记表优先(source: native),磁盘缓存次之(source: cache),最后才按序解析 profile patch 与 settings.yaml / settings.yaml.imported(source: yaml)。DSH_SUBAGENT_CATALOGUE=off 可关掉探针只走兜底 |
| 策略文件 | 路径为 ~/.dsh/subagent/config/model-policy.md。顶部声明 默认模型: <模型 id> 与 默认推理强度: <档位>(发布默认 deepseek-flash / max),## 预设方案 下包含各 ### <名字> 分组与 预设默认模型: <模型 id> 行,## 模型选择规则 为说明文本。桥接层每次调用均重新读取该文件(可通过 DSH_SUBAGENT_POLICY 覆盖路径)。dsh_health 的 modelPolicy.defaultReasoningEffort 回显解析到的档位 |
| 访问链接 | 通过 dsh_health 返回 modelPolicy.path 与 modelPolicy.fileUrl(如 file:///C:/Users/…/model-policy.md,使用 modelPolicy 统一管理) |
| 预设方案 | 包含 ### 本项目方案(本项目方案):默认任务使用 deepseek-flash(推理强度 max),文档编写使用 gemini-3.7-flash,复杂任务由主模型直接处理 |
在未配置默认模型时(策略文件中为 默认模型: (未指定)),dsh_task 与 dsh_health 返回内容末尾附带 6 行初始化指引(包含查询模型、获取用户选择、调用 dsh_setup 写入配置及提供策略文件链接四要素)。写入配置后指引自动停用。
// 落盘:只改策略文件里 `默认模型:` 那一行,其余内容与用户改动一律保留
{ "name": "dsh_setup", "arguments": { "default_model": "deepseek-v4.1-flash" } } // 已接入的模型,裸 id 或 provider/id
{ "name": "dsh_setup", "arguments": { "preset": "本项目方案" } } // 用预设里声明的模型
{ "name": "dsh_setup", "arguments": { "policy_markdown": "…" } } // 整体替换正文(必须保留 `默认模型:` 行)
若传入未注册模型 ID 则返回 isError:true 并输出可用模型列表;若参数类型错误(如 default_model: 3)同样返回 isError:true 并指明错误参数名。可通过 dsh_setup(default_model: "…") 写入配置。
| 外部工具 | 配置文件路径 | 写入内容 |
|---|---|---|
| Cursor(IDE + cursor-agent CLI) | ~/.cursor/mcp.json | mcpServers.dsh |
| Cursor 用户级规则 | ~/.cursor/rules/dsh-subagent.mdc | 「委派子任务优先用 DSH」 |
| Claude Code | ~/.claude.json | 用户级 mcpServers.dsh |
| Claude Code 子代理定义 | ~/.claude/agents/dsh.md | Task(subagent_type: "dsh") = 转发给 DSH |
| Claude Code 全局说明 | ~/.claude/CLAUDE.md | 「子代理委派:统一走 DSH」 |
| Claude Code 工具权限 | ~/.claude/settings.json | permissions.allow += mcp__dsh |
| Claude Desktop(若已安装) | %APPDATA%\Claude\claude_desktop_config.json | mcpServers.dsh |
| Codex CLI / Desktop | ~/.codex/config.toml | [mcp_servers.dsh](含 startup_timeout_sec = 60 / tool_timeout_sec = 600) |
| Codex 全局说明 | ~/.codex/AGENTS.md | 「子代理委派:统一走 DSH」 |
| Gemini CLI | ~/.gemini/settings.json | mcpServers.dsh |
| Antigravity | ~/.gemini/antigravity/mcp_config.json | mcpServers.dsh |
| Kiro | ~/.kiro/settings/mcp.json | mcpServers.dsh |
| Qoder | ~/.qoder/mcp.json | mcpServers.dsh |
| VS Code / Copilot | %APPDATA%\Code\User\mcp.json | servers.dsh |
| Copilot CLI 备用路径 | ~/.vscode/mcp.json | servers.dsh |
| opencode | ~/.config/opencode/opencode.json | mcp.dsh |
| 命令行脚本路径 | ~/.local/bin/dsh-subagent.cmd、dsh-subagent-mcp.cmd | 用户 CLI 入口 |
| DSH 本体 Profile | $DSH_HOME/profiles/subagent/(位于 $DSH_HOME/profiles/ 目录下) | 独立子代理 Profile |
| DSH Desktop 宿主配置 | $DSH_HOME/profiles/desktop/cordis.patch.yml(~/.dsh/profiles/desktop/cordis.patch.yml) | 包含 >>> dsh-subagent-observer >>> 与 >>> dsh-subagent-panel >>> 受管块 |
| DSH Desktop 插件本体 | $DSH_HOME/subagent/monitor/observer.mjs、$DSH_HOME/subagent/gui/** | 宿主监控扩展脚本 |
配置写入执行幂等更新,修改前备份为 <原文件>.bak-dshsubagent-<时间戳>,遇到无法解析的 JSON 文件自动跳过。
high)针对 llm-pi-ai 下你自己的路由,处理某个模型(例如 deepseek-v4.1-flash)的推理强度配置。在 DSH 的设置里声明支持档位与默认强度(0.1.7 之前是 ~/.dsh/settings.yaml;之后设置由 profile 的 patch 层承载,例如 $DSH_HOME/profiles/desktop/cordis.patch.yml 里 llm-pi-ai 条目的 config.providers):
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: MY_GATEWAY_API_KEY
api: openai-responses
baseURL: https://your-gateway.example.com/v1
reasoning: high # 路由默认推理强度:没有显式 --reasoning-effort 时就用它
models:
- id: deepseek-v4.1-flash
reasoningEfforts: # 声明"这个模型支持哪些档位",并给出各档位落到协议上的写法
off: none # 网关不接受空值,off 必须显式写成 none
minimal: minimal
low: low
medium: medium
high: high
xhigh: xhigh
max: max
要点解析:
75 这个取值。 网关只认 none|minimal|low|medium|high|xhigh|max,传 75 会被拒
(unknown variant \75`,HTTP 400)。"75" 是 harness 侧的显示刻度,落到这个网关就是 high(与 Codex 的model_reasoning_effort = "high"同档)。本机的vision-toolkit`、以后新增的服务商都可以按此规则配置。reasoningEfforts 用于声明模型支持的档位列表,缺少声明时调用 --reasoning-effort high 会报 UNSUPPORTED_REASONING_EFFORT;reasoning 决定未传参时的默认级别(此处固定为 effort: "high")。网关实测未声明时 reasoning_tokens = 103(reasoning_tokens=103),显式指定时 reasoning_tokens = 35(reasoning_tokens=35)。dsh_task 的 model 参数或 dsh --profile subagent --reasoning-effort low --prompt ... 调整。实测验证:
# 改之前:失败 —— 说明这个模型压根没声明推理能力(连 high 都不支持)
dsh --profile subagent --prompt "只回答两个字:好的" --reasoning-effort high
# → exit 1, {"code":"UNSUPPORTED_REASONING_EFFORT","message":"... does not support reasoning effort \"high\""}
# 改之后:成功
dsh --profile subagent --prompt "只回答两个字:好的" --reasoning-effort high
# → exit 0, meta.json 里 {"reasoningEffort":"high","stopReason":"completed"}
若配置出现格式错误,dsh-settings-file 将整段拒绝加载并直接报错(NO_ADAPTER: no adapter registered for provider "my-gateway"),不进行静默回退。
安装与卸载脚本(由 install.mjs 与 uninstall.mjs 托管,版本号同步于 package.json):
node $env:USERPROFILE\.dsh\subagent\install.mjs # 幂等重装(推荐)
node $env:USERPROFILE\.dsh\subagent\install.mjs --dry-run # 只看会改什么
node $env:USERPROFILE\.dsh\subagent\uninstall.mjs # 摘掉所有 MCP 注册
subagent Profile 结构与限制Profile 部署于 $DSH_HOME/profiles/subagent/(源码位于 profile/):
package.json bundles = dsh-base + dsh-headless,patchReload = startup
cordis.patch.yml 在 headless 基线上做的 7 处改动(逐条有注释)
subagent-startup.js 任务解析:--prompt / --prompt-stdin / --prompt-file / 位置参数
subagent-runner.js 一次性 runner:跑一轮 → 写 result.txt / meta.json → 退出
相对于官方 Headless 的调整:
--prompt-stdin、--result-file、--metadata-file、--model。{sessionId, model, stopReason, durationMs} 元数据,支持单次调用指定模型;模型没有点名 provider 时按推理档位选路(见 §2 的 model / provider 行)。never。DSH_SUBAGENT_PERMISSION 环境变量控制(默认 workspace-write,即工作区可读写、工作区外拒绝)。approval: never,workspace-write 不会因预设不匹配而启动失败)。DSH_TOOLS_MODE=ptc(tools 行读这个变量;可用 DSH_SUBAGENT_TOOLS_MODE=native 改回原生工具调用)。外部工具通过 dsh_task 调用的任务禁止递归创建子代理,Profile 禁用了以下插件:
| 禁用插件项 | 移除的工具接口 |
|---|---|
tool-subagent | subagent |
tool-subagent-fork | subagent_fork |
tool-workflow + workflow-worker-thread | workflow(脚本内 agent() 扇出) |
tool-ralph | ralph |
tool-subagent-control + tool-subagent-list-agents | list_agents / send_message / interrupt_agent |
会话下发的工具表比对(解压 jsonl.zstd 验证):
| 检查状态 | 可用工具总数 | 分叉类工具清单 |
|---|---|---|
| 限制前 | 25 | subagent, subagent_fork, workflow, ralph, list_agents, send_message, interrupt_agent |
| 限制后 | 18 | 上述 7 个分叉工具全部移除;保留 pwsh / read / write / edit / grep / web_search / todo_write 等操作工具 |
自检验证命令:
node $env:USERPROFILE\.dsh\subagent\test\leaf-only-probe.mjs <sessionId片段>
命令行直接调用测试:
cd D:\some\repo
dsh --profile subagent --prompt "跑一遍单测并汇总失败项"
# 长提示词走管道,不受命令行长度限制:
type task.md | dsh --profile subagent --prompt-stdin
| 关注维度 | 执行现状 |
|---|---|
| 默认权限 | workspace-write(目标工作区可读写;工作区之外由沙箱拒绝) |
| 放宽方式 | 传入参数 permission: "danger-full-access"(全盘读写)或设置环境变量 DSH_SUBAGENT_PERMISSION |
| 审批模式 | 子代理进程固定为 never,越界操作由沙箱直接拒绝 |
| 并发控制 | 单个 MCP 服务进程默认最多 4 个并发 DSH 实例,超出排队等待(详见 §6.1) |
| 审计记录 | 每次任务持久化至 $DSH_HOME/subagent/state/tasks/<job-id>/{prompt.md,result.txt,meta.json,stderr.log,task.json} |
| 遥测设置 | 遵循 DSH 自身配置,可设置 DSH_TELEMETRY_DISABLED=1 禁用 |
权限生效机制:
approval: never),避免 dsh-permission-presets 在构造期抛出 composed sandbox and approval defaults match no preset 异常导致进程退出。permissionPresets.set() 将目标权限写入当前会话事件流,工具层依据 ctx.sandboxPolicy.resolve({session}) 动态解析沙箱策略;若锁定失败则直接报错退出。已知运行边界:两个 profile 的沙箱相关行集合一致(sandbox-local / sandbox-policy / pwsh-sandbox / fs-sandbox)。DSH 0.2.1 上 workspace-write 与 read-only 模式下 pwsh 工具可正常执行(实测 Write-Output SPAWN-PING 返回 "SPAWN-PING\r\n"、退出码 0、sandbox.mode=workspace-write、denied=false);此前 0.1.x 出现过的「受限模式下 Shell 返回空文本」已在 0.2.1 消失,但 0.1.x 的旧实例仍可能复现。
0.1.x 的 workspace-write / read-only 曾出现 Shell 工具返回空内容("\r\n")且 isError: false、进程并未实际启动的情况:
| 权限等级 | 0.1.x 实测命令返回数据 | 0.2.1 实测 |
|---|---|---|
danger-full-access | SPAWN-PING\r\nELAPSED_MS=59\r\n | 正常 |
workspace-write / read-only | "\r\n"(isError: false) | SPAWN-PING\r\n、退出码 0(正常) |
桥接层仍保留这段兜底:任务结束时会解析会话日志,检测这类空转的 Shell 调用;若存在,会在答复末尾附加调用次数与命令样例的告警,并在元数据中记录 shellHollowCalls 与 shellHollowSamples。所以「任务跑了但命令没执行」不会被伪装成成功。
| 控制维度 | 执行机制 |
|---|---|
| 单次会话并发请求 | 支持并行处理。MCP 层基于异步事件(lib/mcp.mjs 中的 onFrame)分派任务,互不阻塞 |
| 单进程并发上限 | 默认上限为 4(由 DSH_SUBAGENT_MAX_CONCURRENCY 控制),超出限制的任务进入队列排队等待 |
| 多客户端隔离 | 每个客户端工具运行独立的 MCP 服务进程,各享有独立的并发配额(CLI dsh-subagent 同样独立) |
| 配额调整 | 在客户端配置的 env 中添加 "DSH_SUBAGENT_MAX_CONCURRENCY": "8",重启客户端生效 |
| 状态查询 | 通过 dsh_health 返回 maxConcurrency、activeTasks、activeByCaller(如 cursor-vscode: 2 running)、liveTasks(包含 job_id/caller/workspace/status/pid/deadlineAt/remainingSeconds/lastProgressAt/progressBytes/queuedSeconds)及最近任务列表 |
实测通过 node test/concurrency-probe.mjs <工作空间> 3(node test/concurrency-probe.mjs <ws> 3)验证 3 路并发正常并行执行。
任务取消与终止:
dsh_task_cancel:优雅取消,对运行中及排队中的任务均有效。dsh_task_kill:强制终止指定 job_id 或指定 caller 在当前服务进程内的全部活跃任务进程树。| 保护机制 | 触发条件 | 执行动作与状态 |
|---|---|---|
| 外层超时上限 | 达到 timeout_seconds(默认 1800) | 终止任务,记录 status: "timeout" |
| 预估截止时间 | 达到 startedAt + expected_seconds × grace(默认系数为 2) | 终止任务,记录 status: "deadline",返回 error: 超出预估时间 Ns × grace G 仍未完成,已终止 |
| 停滞看门狗 | 每 |