🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

子代理桥 xDJTomato/deepseek-harnessed

DeepSeek Harness
xDJTomato18 天前有提交已提供中文文档扫描:含敏感能力

让各类 AI 客户端通过 MCP 调用 DSH 执行任务

29.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/6 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add xDJTomato/deepseek-harnessed
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · model
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已真实安装成功
非静态推断
L4 · 真实安装等级说明 →
数据截至 2026/09/25·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/06·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 18 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查需源码安装
要求 Node:>=24(dsh 本身要求 Node ≥ 22.19)

engines.node 要求 >=24,不满足 Node 22.19.0;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包deepseek-harnessed(未发布到 npm,仅可源码安装)
✗Node 引擎要求 >=24 · 基线 Node 22.19 不满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/9/21 23:55:43

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 117 处,展示前 73 条)
  • 读取本地文件
    bin/dsh-subagent.mjs:125
    prompt = readFileSync(options.promptFile, 'utf8');
  • 读取本地文件
    lib/tasks.mjs:131
    stat = statSync(path);
  • 执行外部命令固定命令
    lib/tasks.mjs:144
    const killer = spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
  • 执行外部命令固定命令
    lib/tasks.mjs:165
    execFileSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
  • 读取本地文件
    lib/tasks.mjs:175
    return statSync(path).size;
  • 执行外部命令来自配置/变量
    lib/tasks.mjs:318
    const stdout = execFileSync(shell, ['-NoProfile', '-NonInteractive', '-Command', script], {
  • 执行外部命令来自配置/变量
    lib/tasks.mjs:1140
    child = spawn(launcher.command, args, {
  • 读取本地文件
    lib/launcher.mjs:51
    const content = readFileSync(path, 'utf8');
  • 读取本地文件
    lib/launcher.mjs:122
    if (existsSync(args[index])) return true;
  • 读取本地文件
    lib/launcher.mjs:124
    if (archive === undefined || !existsSync(archive) || !statSync(archive).isFile()
  • 读取本地文件
    lib/launcher.mjs:124
    if (archive === undefined || !existsSync(archive) || !statSync(archive).isFile()
  • 读取本地文件
    lib/launcher.mjs:125
    || env.ELECTRON_RUN_AS_NODE !== '1' || !existsSync(command)) return false;
  • 执行外部命令来自配置/变量
    lib/launcher.mjs:127
    const probe = spawnSync(command, [...args, '--version'], {
  • 读取本地文件
    lib/launcher.mjs:142
    if (!existsSync(candidate)) continue;
  • 执行外部命令来自配置/变量
    lib/model-catalogue.mjs:136
    const result = spawnSync(probe.command, probe.args, {
  • 写入本地文件
    lib/util.mjs:33
    mkdirSync(path, { recursive: true });
  • 读取本地文件
    lib/util.mjs:46
    return JSON.parse(readFileSync(path, 'utf8'));
  • 写入本地文件
    lib/util.mjs:57
    writeFileSync(tmp, text, 'utf8');
  • 写入本地文件
    lib/util.mjs:59
    renameSync(tmp, path);
  • 删除本地文件
    lib/util.mjs:62
    unlinkSync(path);
  • 写入本地文件
    lib/util.mjs:63
    renameSync(tmp, path);
  • 写入本地文件
    lib/util.mjs:65
    writeFileSync(path, text, 'utf8');
  • 读取本地文件
    lib/util.mjs:73
    return readFileSync(path, 'utf8');
  • 写入本地文件
    lib/util.mjs:82
    writeFileSync(path, content, 'utf8');
  • 读取本地文件
    lib/util.mjs:108
    return readdirSync(path).map((name) => ({ name, path: join(path, name) }));
  • 读取本地文件
    lib/util.mjs:117
    return statSync(path).mtimeMs;
  • 读取本地文件
    lib/mcp.mjs:43
    const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
  • 发起网络请求
    lib/mcp.mjs:842
    const result = await this.request('roots/list', {}, 3000);
  • 写入本地文件
    lib/mcp.mjs:1359
    appendFileSync(lifecyclePath(), `${new Date().toISOString()} pid=${process.pid} ${line}\n`);
  • 读取本地文件
    lib/session-log.mjs:25
    if (!existsSync(sessionDir)) return [];
  • 读取本地文件
    lib/session-log.mjs:29
    entries = readdirSync(sessionDir);
  • 读取本地文件
    lib/session-log.mjs:37
    const stat = statSync(path);
  • 读取本地文件
    lib/session-log.mjs:57
    buckets = readdirSync(root);
  • 读取本地文件
    lib/session-log.mjs:77
    raw = readFileSync(file);
  • 写入本地文件
    lib/monitor-host.mjs:96
    mkdirSync(join(dshHome(), 'subagent', 'state'), { recursive: true });
  • 写入本地文件
    lib/monitor-host.mjs:97
    appendFileSync(monitorHostLogPath(), text, 'utf8');
  • 执行外部命令来自配置/变量
    lib/monitor-host.mjs:169
    const out = execFileSync(shell, ['-NoProfile', '-NonInteractive', '-Command', script], {
  • 读取本地文件
    lib/monitor-host.mjs:217
    if (existsSync(candidate)) return { exe: candidate, args: [], source: 'default-path' };
  • 执行外部命令来自配置/变量
    lib/monitor-host.mjs:343
    const child = spawn(command.exe, command.args, { detached: true, stdio: 'ignore', windowsHide: false });
  • 读取本地文件
    install.mjs:61
    if (!existsSync(path)) return;
  • 写入本地文件
    install.mjs:64
    copyFileSync(path, bak);
  • 读取本地文件
    install.mjs:70
    return JSON.parse(readFileSync(path, 'utf8'));
  • 写入本地文件
    install.mjs:81
    writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
  • 读取本地文件
    install.mjs:110
    const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
  • 读取本地文件
    install.mjs:110
    const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
  • 写入本地文件
    install.mjs:127
    if (!dryRun) writeFileSync(path, next, 'utf8');
  • 读取本地文件
    install.mjs:142
    if (!existsSync(path)) {
  • 读取本地文件
    install.mjs:152
    const raw = readFileSync(path, 'utf8');
  • 写入本地文件
    install.mjs:212
    if (!dryRun) writeFileSync(path, next, 'utf8');
  • 写入本地文件
    install.mjs:349
    if (!dryRun) copyFileSync(from, to);
  • 执行外部命令来自配置/变量
    install.mjs:378
    const result = spawnSync(launcher.command, [...launcher.args, '--profile', 'subagent', '--dump-config'], {
  • 写入本地文件
    install.mjs:446
    copyFileSync(source, target);
  • 读取本地文件
    monitor/observer.mjs:144
    return JSON.parse(readFileSync(path, 'utf8'));
  • 读取本地文件
    monitor/observer.mjs:162
    const size = statSync(path).size;
  • 读取本地文件
    monitor/observer.mjs:219
    return readFileSync(path, 'utf8');
  • 读取本地文件
    monitor/observer.mjs:273
    const raw = readFileSync(file);
  • 读取本地文件
    monitor/observer.mjs:311
    if (cached.path === null || existsSync(cached.path)) {
  • 读取本地文件
    monitor/observer.mjs:318
    for (const bucket of readdirSync(SESSIONS_ROOT)) {
  • 写入本地文件
    monitor/observer.mjs:591
    appendFileSync(LOG_PATH, `${new Date().toISOString()} ${line}\n`, 'utf8');
  • 写入本地文件
    monitor/observer.mjs:721
    mkdirSync(stateRoot, { recursive: true });
  • 写入本地文件
    monitor/observer.mjs:724
    writeFileSync(heartbeatPath, `${JSON.stringify({
  • 写入本地文件
    profile/subagent-runner.js:166
    writeFileSync(path, content, 'utf8');
  • 读取本地文件
    profile/subagent-startup.js:125
    publish(readFileSync(options.promptFile, 'utf8'));
  • 写入本地文件
    uninstall.mjs:31
    copyFileSync(path, `${path}.bak-dshsubagent-${STAMP}`);
  • 写入本地文件
    uninstall.mjs:32
    writeFileSync(path, content, 'utf8');
  • 读取本地文件
    uninstall.mjs:37
    if (!existsSync(path)) return;
  • 读取本地文件
    uninstall.mjs:40
    file = JSON.parse(readFileSync(path, 'utf8'));
  • 读取本地文件
    uninstall.mjs:58
    if (!existsSync(path)) return;
  • 读取本地文件
    uninstall.mjs:59
    const text = readFileSync(path, 'utf8');
  • 读取本地文件
    uninstall.mjs:75
    if (!existsSync(path)) return;
  • 读取本地文件
    uninstall.mjs:76
    const text = readFileSync(path, 'utf8');
  • 删除本地文件
    uninstall.mjs:134
    if (!dryRun) rmSync(file, { force: true });
  • 删除本地文件递归
    uninstall.mjs:142
    if (!dryRun) rmSync(target, { recursive: true, force: true });
扫描于 2026/11/6(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 通过MCP调用DSH
  • DSH实例转为子代理
  • 实时监控任务执行

把 DeepSeek Harness(DSH)实例变成子代理,任何 harness(Cursor / Claude Code / Codex / Gemini CLI……)都能通过 MCP 调用,还能在 DSH Desktop 里实时监控。

README

由 DeepSeek 最新模型翻译生成
deepseek-harnessed 把整个 DeepSeek Harness (DSH) 运行实例包装成子代理(subagent),供 Cursor / Claude Code / Codex / Gemini CLI / Antigravity / Kiro / Qoder / VS Code (Copilot) / opencode 等调用方工具调用;同时为 DSH Desktop 宿主界面提供实时状态监控悬浮卡片。 架构设计说明:让各类外部 Agent 工具把子任务分派给独立的本地 DSH 实例执行,而不是只在自身进程内跑轻量级函数调用。 系统由以下三部分组成: | 模块 | 目录位置 | 技术说明 | | MCP 桥接层 | bin/ + lib/ | 标准 Stdio MCP 服务器,对外暴露 dsh_task 等六个工具;每次分派拉起独立的 DSH 进程 | | DSH subagent profile | profile/ | 配置 DSH 进程以单次会话、无人值守、只做叶子节点模式运行 | | GUI 宿主插件 | monitor/ + gui/ | 观察器把外部任务的实时状态灌进会话投影;悬浮卡片按调用方分组显示 | 快速导航文档 | 文档路径 | 涵盖内容 |
展开完整文档(README 剩余 1 节)
deepseek-harnessed

把整个 DeepSeek Harness (DSH) 运行实例包装成子代理(subagent),供 Cursor / Claude Code / Codex / Gemini CLI / Antigravity / Kiro / Qoder / VS Code (Copilot) / opencode 等调用方工具调用;同时为 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 版本兼容性

桥接层运行于 DSH 内部环境(包含一个 Profile 与两个宿主插件),上游接口变动将直接影响插件行为。以下为实测适配数据(基于从 app.asar 提取的实际运行源码验证,避免 app.asar.unpacked/ 中陈旧文件干扰):

| DSH 版本 | 兼容状态 | 说明 |
| 0.1.5-rc.1 | ✅ 已完成全量适配与验证 | 处理了 3 处破坏性接口变更(见下表) |
| 0.1.4 及更早版本 | ✅ 保持兼容 | 三处接口变更均包含向前兼容逻辑(见下) |

0.1.5-rc.1 的三处破坏性变化与处理方案

| 变更项 | 影响表现 | 适配方案 |
| @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 改默认级别。
- 默认工具形态:叶子进程以 PTC(程序化工具调用) 形态运行——模型写一段 TypeScript,经 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 文件自动跳过,不覆盖现有数据。
- 运行平台:当前仅针对 Windows 环境开发与验证,依赖 taskkill 与 PowerShell 进程管理机制。

系统工作原理

本机各智能体工具通过 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 系统进程运行,拥有独立的上下文窗口、模型配置、工具链以及持久化会话记录,而不是宿主工具内的函数调用。

1. 一分钟自检

桥接是否可用(等价于 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

2. 对外暴露的六个 MCP 工具

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,此时外部智能体应在同一会话中调用 dsh_task_status(job_id, wait_seconds=30) 等待并轮询(每次等待 30 秒),直至达到终态(ok / error / deadline / stalled / cancelled / killed)。请勿直接传入 wait_seconds=0,避免过早拆成多次模型轮询往返。

委托说明前缀:桥接层默认在提示词前添加简要委托指引(明确为单次会话、无人交互应答、结束时汇报修改文件与结果结论),防止 DSH 进程因等待用户交互而挂起。

2.1 工具调用规范与 Schema 说明

操作约定已直接固化到 MCP 工具 Schema 定义中:

| 定义位置 | 规范内容 |
| initialize.instructions | 包含 initialize 带委派操作手册 概要(≤1500字符):时间预估、验收标准、短超时优先等待、状态流转与失败排查说明 |
| dsh_task.description | 包含 dsh_task 描述含完整委派约定(约 3250 字符):先拆再派的原则与阈值(一次只派一个可独立验收的产物、expected_seconds ≤ 300s;超过 300s、要改 3 个以上文件、或属于「先调研再实现再验证」的多阶段作业必须先拆;互不依赖的子任务同一轮里并发派出,有依赖的才串行)、禁止委派的清单(短指令作业自己做;dev server / watch / daemon / 交互式终端 / 需要成对收尾的独占资源不派 —— 收尾会连进程树一起带走,常驻的东西由调用方自己起后台作业)、明确的执行上限与经验区间(单文件 60~180s、多文件特性 300~900s、重构 900~1800s 且需拆分)、deadlineAt = 开始时刻 + 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 文案的完整断言校验,确保规范描述不丢失。

2.2 任务终止响应机制

外部客户端取消当前轮次请求时,客户端会发送 MCP notifications/cancelled 通知。桥接层执行以下终止逻辑:

| 触发场景 | 桥接层处理逻辑 |
| 取消正在等待的 dsh_task 或 dsh_task_status 请求 | 精确终止该请求对应的单个子代理任务 |
| 收到未关联具体请求的取消通知 | 若当前连接仅有一个活跃任务在运行,则终止该任务 |
| 关闭 MCP 连接或关闭外部客户端 | 终止当前连接名下的所有活跃任务并退出服务进程 |
| 终止 MCP 服务进程(SIGTERM/SIGINT) | 终止本进程名下的全部任务后退出 |

服务不依据轮询空闲时长猜测连接状态,仅响应明确的停止信号。在 Windows 环境下通过 taskkill /F 或 TerminateProcess 强制终止外部进程,并通过测试确保取消通知到达后子进程实际退出。

2.3 执行等待策略与超时控制

长任务若默认采用 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 查看生效值。修改后需重启客户端生效。

2.4 模型配置机制与策略文件

桥接层动态获取模型清单,模型选择规则保存在外部策略文件中:

| 环节 | 技术实现与定义 |
| 已接入模型 | 通过 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/idjson { "name": "dsh_setup", "arguments": { "preset": "本项目方案" } } // 用预设里声明的模型 { "name": "dsh_setup", "arguments": { "policy_markdown": "…" } } // 整体替换正文(必须保留 默认模型: 行)

传入未注册的模型 ID 时,返回 isError:true,并输出可用模型列表;参数类型错误(如 default_model: 3)同样返回 isError:true,并指明出错的参数名。可通过 dsh_setup(default_model: "…") 写入配置。

3. 本机配置注册详情

| 外部工具 | 配置文件路径 | 写入内容 |
| 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 文件自动跳过。

3.1 推理强度配置(固定为 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): yaml 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 Desktop。
- 单次调用可以通过 dsh_task 的 model 参数调整,也可以用 dsh --profile subagent --reasoning-effort low --prompt ...。

实测验证: powershell

改之前:失败 —— 说明这个模型压根没声明推理能力(连 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): powershell
node $env:USERPROFILE\.dsh\subagent\install.mjs # 幂等重装(推荐)
node $env:USERPROFILE\.dsh\subagent\install.mjs --dry-run # 只看会改什么
node $env:USERPROFILE\.dsh\subagent\uninstall.mjs # 摘掉所有 MCP 注册

4. DSH subagent 配置档结构与限制

配置档部署于 $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 的调整:

1. 启动器支持 --prompt-stdin、--result-file、--metadata-file、--model。
2. 运行器输出最终答复与 {sessionId, model, stopReason, durationMs} 元数据,支持单次调用指定模型;模型没有点名服务商时按推理档位选路(见 §2 的 model / provider 行)。
3. 停用标题自动生成插件,减少模型调用。
4. 审批策略固定为 never。
5. 沙箱模式由 DSH_SUBAGENT_PERMISSION 环境变量控制(默认 workspace-write,即工作区可读写、工作区外拒绝)。
6. 显式声明无人值守权限预设表(三种模式都对应 approval: never,workspace-write 不会因预设不匹配而启动失败)。
7. 工具形态默认 PTC:起叶子进程时设 DSH_TOOLS_MODE=ptc(tools 行读这个变量;可用 DSH_SUBAGENT_TOOLS_MODE=native 改回原生工具调用)。
8. 施加叶子节点限制条件,禁止外部任务递归分派子代理。

4.1 叶子限制条件(禁止外部任务递归派生子代理)

外部工具通过 dsh_task 调用的任务禁止递归创建子代理,配置档禁用了以下插件:

| 禁用插件项 | 移除的工具接口 |
| 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 等操作工具 |

自检验证命令: powershell
node $env:USERPROFILE\.dsh\subagent\test\leaf-only-probe.mjs <sessionId片段>

命令行直接调用测试: powershell
cd D:\some\repo
dsh --profile subagent --prompt "跑一遍单测并汇总失败项"

长提示词走管道,不受命令行长度限制:

type task.md | dsh --profile subagent --prompt-stdin

5. 权限管理与沙箱机制

| 关注维度 | 执行现状 |
| 默认权限 | 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 禁用 |

权限生效机制:

1. Profile 显式定义完整的权限预设映射表(三种模式均对应 approval: never),避免 dsh-permission-presets 在构造期抛出 composed sandbox and approval defaults match no preset 异常导致进程退出。
2. 运行器在执行前通过 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 的旧实例仍可能复现。

5.1 沙箱模式下 Shell 空转检测(历史兜底,0.2.1 起不再触发)

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。所以「任务跑了但命令没执行」不会被伪装成成功。

6. 并发控制与状态可视化

6.1 并发与任务生命周期管理

| 控制维度 | 执行机制 |
| 单次会话并发请求 | 支持并行处理。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 在当前服务进程内的全部活跃任务进程树。

6.2 超时保护与停滞看门狗

| 保护机制 | 触发条件 | 执行动作与状态 |
| 外层超时上限 | 达到 timeout_seconds(默认 1800) | 终止任务,记录 status: "timeout" |
| 预估截止时间 | 达到 startedAt + expected_seconds × grace(默认系数为 2) | 终止任务,记录 status: "deadline",返回 error: 超出预估时间 Ns × grace G 仍未完成,已终止 |
| 停滞看门狗 | 每 DSH_SUBAGENT_WATCHDOG_INTERVAL(默认 30 秒)采样一次,满足多信号无增长、静默时间达 DSH_SUBAGENT_STALL_MIN_SECONDS(默认 180 秒)且连续 DSH_SUBAGENT_STALL_PROBES(默认 2)次采样无变化 | 终止进程树,记录 status: "stalled" 并保存取证数据(signalState / treePids / treeCpuMs / cpuDeltaMs / silenceSeconds) |

停滞判定的信号指标

单看日志大小不足以判定任务是否停滞(例如长时间执行复杂循环脚本时,日志可能暂时不增长)。因此采样以下综合指标:

| 信号类别 | 检测指标 | 说明 |
| 输出日志 | stderr.log / stdout.log / result.txt / session.jsonl.zstd 的字节数 | 只比对文件字节大小,不用不可靠的修改时间(mtime) |
| 进程树状态 | ① 根进程存活状态 ② 后代进程 PID 集合变动 ③ 整树累计 CPU(KernelModeTime + UserModeTime)增长是否达到 DSH_SUBAGENT_STALL_CPU_MS(默认 200ms) | 单轮通过 Get-CimInstance(Get-CimInstance Win32_Process)查询全表快照,未达阈值的增量累加 |
| 外部观测 | 调用方通过 dsh_task_status 读取到的 progressBytes 增量 | 外部状态查询产生的数据推进 |

运行规则与护栏:

- 排队中的任务不做停滞判定,用 queuedSeconds 监控排队时长。
- 首个探测周期完成前不执行终止;已结束或已取消的任务不处理。
- 只依据字节增长判定进展,时间戳变化不计入进展。
- 后代进程活跃或 CPU 增长的长命令视为正常执行。
- 需同时满足静默时长达到 DSH_SUBAGENT_STALL_MIN_SECONDS 且连续 DSH_SUBAGENT_STALL_PROBES 次采样无进展,才触发终止。
- 引入 CPU 干活强度下限 DSH_SUBAGENT_STALL_CPU_WORK_FLOOR_MS(默认 1500ms),把调度与定时器空转噪声和实际计算工作区分开。
- 对照实验证明:设置 DSH_SUBAGENT_STALL_CPU_MS=200 (且不设 ..._CPU_WORK_FLOOR_MS) 跑同一个自检时,小幅 CPU 增量会使 progressed 标志置位,导致 stalledProbes 采样计数反复归零而漏判挂起;设置下限阈值后可准确认定停滞。
- 快速测试可设置 DSH_SUBAGENT_WATCHDOG_INTERVAL=5 与 DSH_SUBAGENT_STALL_MIN_SECONDS=10;设置 DSH_SUBAGENT_WATCHDOG=off 可整体关闭看门狗。

6.3 宿主会话列表可见性

子代理创建的会话保存在 $DSH_HOME/sessions/<工作空间key>/session-<uuid>/ 目录下(例如工作空间 D:\work\demo 对应目录为 $DSH_HOME\sessions\--D-work-demo--\,路径特殊字符编码为 ~XXXX~ 形式)。会话数据随执行实时落盘,可在 DSH Desktop 列表中查阅。

实时查看任务输出: powershell

推理流(实时增长)

Get-Content -Wait $env:USERPROFILE\.dsh\subagent\state\tasks\<job-id>\stderr.log

当前状态 / 工作空间 / 产物

Get-Content $env:USERPROFILE\.dsh\subagent\state\tasks\<job-id>\task.json

任务结束后,result.txt 存放最终答复文本,meta.json 中记录 sessionId。实时查看任务输出也可直接用 Get-Content -Wait …\stderr.log 命令查看。

6.4 宿主状态观察器(monitor/observer.mjs)

观察器插件加载于 DSH Desktop 宿主进程中,把外部任务状态同步到界面:

| 任务状态阶段 | 界面显示状态 |
| 任务启动(约 2 秒内) | 会话自动显示在对应工作空间的侧边栏,并显示运行中标记 |
| 运行中 | 会话置顶显示,标题包含调用方与任务信息(带有 ⚡ <调用方> · <标题> ⚠运行中·勿点开 说明),携带状态投影数据 |
| 任务结束 | 运行中标记清除,会话保留在列表中供完整查阅 |

实现机制:

1. 桥接层在任务启动时将 sessionId 写入 meta.json,并在 task.json 中记录 pid / status / workspace。
2. 观察器每 2 秒扫描 $DSH_HOME/subagent/state/tasks//,校验 pid 存活状态,并发送 api-session/added、api-session/status、api-session/activity(简称 activity)事件。
3. 客户端接收事件后调用 mergeSummary 与 session.handleRunning(running) 更新侧边栏。
4. 携带 projections.values['dsh-subagent'] 投影数据,每 20 秒(或数据变动后经过 ACTIVITY_MS)重播一次,确保页面刷新后状态恢复。

安装文件:

- 插件本体:~/.dsh/subagent/monitor/observer.mjs(依赖 node:fs 与 ctx.emit)。
- 宿主配置:~/.dsh/profiles/desktop/cordis.patch.yml 中的 >>> dsh-subagent-observer >>> 受管块。
- 日志文件:~/.dsh/subagent/state/observer.log(设置 DSH_SUBAGENT_OBSERVER_DEBUG=1 可开启调试输出)。

配置生效要求:desktop 配置档的 patchReload: live 机制在打包环境下不会自动热加载,首次安装后需要重启一次 DSH Desktop。

观察器每 5 秒更新一次心跳文件 $DSH_HOME/subagent/state/observer-heartbeat.json: json { "at": "2026-09-11T10:31:02.123Z", "epochMs": 1762331462123, "pid": 12345, "engine": "dsh-desktop-observer", "profile": "desktop", "pollMs": 2000, "trackedTasks": 3, "activeTasks": 1 }

宿主自动启动决策流程(lib/monitor-host.mjs):

| 步骤 | 判定条件 | 执行操作 |
| 1 | 心跳时间有效(Date.now() - epochMs < 20000)且心跳记录的 PID 存活 | 标记 already-running,不启动新宿主 |
| 2 | 心跳文件存在但 PID 已退出或时间过期 | 判定为过期记录,继续后续检查 |
| 3 | 环境变量 DSH_SUBAGENT_AUTOSTART_MONITOR=off | 标记 skipped |
| 4 | 运行平台非 Windows | 标记 skipped |
| 5 | 枚举进程中已存在真正的图形界面宿主进程 | 标记 gui-open-old-observer,不重复启动 |
| 6 | 进程表查询连续失败两次 | 标记 probe-failed,暂不启动 |
| 7 | 距上次启动尝试未超过冷却时间(DSH_SUBAGENT_MONITOR_COOLDOWN_MS,默认 60000) | 标记 skipped |
| 8 | 上述条件均不满足 | 标记 launched,调用 spawn(exe, args, {detached:true, stdio:'ignore', windowsHide:false}).unref() 后台启动 |
| — | 找不到可执行文件 | 标记 unavailable |
| — | 仅执行只读状态查询 | 标记 unknown(dsh_task_status 接口保持只读) |

进程分类规则(与 test/live-audit.mjs 一致):命令行包含 --type= 判定为渲染子进程;包含 --expose-internals 判定为 CLI 子代理任务;两者均不包含判定为图形宿主主进程(阻止重复启动)。命令行是用 base64 编码从 PowerShell 传递的(直接使用 "$pid|$cmdline" 会被多行脚本 node -e "…" 的换行符切断并解析出 pid=NaN 假条目)。可通过 DSH_SUBAGENT_MONITOR_FORCE=1 强制启动,或设置 DSH_SUBAGENT_MONITOR_HOST_CHECK=off 关闭宿主检测。

可执行文件定位顺序:DSH_SUBAGENT_MONITOR_CMD(可通过 DSH_SUBAGENT_MONITOR_ARGS 追加参数)→ C:\Program Files\DSH Desktop\DSH Desktop.exe → %LOCALAPPDATA%\Programs\DSH Desktop\DSH Desktop.exe。日志记录于 $DSH_HOME/subagent/state/monitor-host.log。状态可通过 dsh_health 中的 monitorHost(包含 running、state(状态包括 host-with-heartbeat / host-older-observer / no-host)、guiOpen、guiHostPids、cliProcessPids、heartbeatAt、heartbeatAgeMs、heartbeatPidAlive、heartbeatResidue、processProbeFailed、autostart、lastLaunch)及任务返回结果中的 monitor_host: <action>(<reason>) 查看。

自检验证命令: powershell
node $env:USERPROFILE\.dsh\subagent\test\monitor-autostart-probe.mjs # 26 项:三步判活逐条 + 命令行分类

配置参数包含 DSH_SUBAGENT_OBSERVER_INTERVAL、DSH_SUBAGENT_OBSERVER_ANNOUNCE_AGE_MS、DSH_SUBAGENT_OBSERVER_REANNOUNCE_MS、DSH_SUBAGENT_OBSERVER_MAX_AGE_MS。

自检测试: powershell
node $env:USERPROFILE\.dsh\subagent\test\observer-selftest.mjs # 50 项,含投影、僵尸/半成品判活、心跳+规则版本、token 折叠、吞吐窗口、只读转录、0.1.5 新日志名
node $env:USERPROFILE\.dsh\subagent\test\live-audit.mjs # 现场审计:真的在跑几个 / 残留记录几个 / 宿主状态

6.5 悬浮卡片 Subagent(独立实时视图)

悬浮面板插件 dsh-subagent-panel 加载于窗口右上角,提供独立的运行状态视图:

| 功能特性 | 技术说明 |
| 分组展示 | 按 Cursor / Claude Code / Codex / 命令行 / 自检 / 未知来源 分组,显示对应字形标识 |
| 内置子代理支持 | 支持展示 DSH 内部的 subagent(subagentsByParent 目录),归入「本机 DSH 子代理 ✦」分组,显示 L1 ⊞ 层级标记与状态 |
| 交互展开 | 点击分组标题展开卡片列表,包含活跃任务的分组默认保持展开 |
| 默认过滤 | 头部默认仅展示运行中会话,点击 全部 展示历史完成记录 |
| 视觉设计 | 独立配色方案,适配浅色与深色主题,符合 WCAG 对比度规范 |
| 界面缩放 | 头部提供 − / 120% / +(缩放栏 − 120% +)按钮,支持 60%~200% 范围调节,数据持久化于 localStorage |
| 自由调整尺寸 | 提供三个调节手柄(右侧调节宽、底部调节高、右下角同时调节),数据保存在 localStorage,双击手柄恢复默认尺寸 |
| 自适应多列布局 | 网格布局使用 repeat(auto-fill, minmax(132px, 1fr)) 随实际宽度自适应列数,高度拉伸后内容区域滚动 |

| 状态汇总统计 | 底部显示 Token 汇总:250 tok/s \| 缓存命中 99% \| 输入 111M tok · 输出 636K tok |
| 空状态展示 | 无活跃任务时居中显示图标与「无活跃子代理」提示文本 |
| 实时详情查看 | 点击运行中任务卡片进入只读详情页,展示调用方、工作空间、已耗时、验收条件、Token 明细及会话转录(用户提示词、助手答复、工具调用、工具返回;观察器只读折叠会话日志得来);没有转录时退回 stderr.log 最新输出行 |
| 历史会话访问 | 点击已完成卡片调用 ctx.sessions.open(id) 打开对应会话 |
| 拖动与折叠 | 头部支持拖动调整位置,点击 收起 后最小化为紧凑状态条 |

头部控件在小尺寸下支持折行(flex-wrap: wrap),采用 flex 布局并保持 flex: none 与 white-space: nowrap,避免将 Subagent 标题截断为 SUBAGENT… 或 SUBAGENT。

7.5.1 底部状态条 Token 统计数据来源

| 会话类型 | 数据源 | 说明 |
| 本机 DSH 子代理 / 宿主自身会话 | 宿主 projectionValues.tokenUsage 投影 | 直接读取宿主自身的 Token 计量数据 |
| 外部 dsh_task 任务 | 观察器解析子代理的会话日志 | 独立解压分析会话日志文件计算用量 |

Token 折叠逻辑遵循同轮次 (turn, step) 的 assistant/chunk(chunk.type=usage) 与 assistant/message(data.usage) 样本执行替换更新(避免用量翻倍),遇到 llm/retry-started 重置替换槽。缓存命中率计算公式为 缓存读 / prompt 侧总量。

数据解压要求:由于会话日志采用多帧 zstd 追加写格式,必须逐帧解压(避免使用 zstdDecompressSync(整个文件) 导致仅解出首帧数据),折叠结果按 (size, mtime) 缓存并以 5 秒周期节流。

7.5.2 吞吐速率(tok/s)计算规则与窗口设定

吞吐速率计算统一在观察器内进行(rateOf):依据真实采样时间跨度计算 Δ输出 / Δ真实采样时间(时间跨度小于 3 秒不计算速率,旧实现直接在客户端计算 Δ输出/Δt 产生严重误差;避免类似 2 秒里跳 6000 token 被误算为 3000 tok/s,跨 29 秒的 6000 token 准确计算为约 207 tok/s),并取最近 4 次采样的平滑平均值。客户端仅负责渲染显示,不保留采样窗口。

实测输出:通过 test/observer-selftest.mjs 验证投影数据中输出速率低于 2000(< 2000 断言),仅运行中任务显示速率(第二行显示 ↑输入 ↓输出 与 N tok/s 吞吐指标,单位 tok/s,待机显示 - 或 —),结束后恢复。

真实日志折叠范例(node test/usage-fold-probe.mjs --all):

1282 帧 / 1727 行 / 699.4K 输入 92103 · 输出 50207 · 缓存读 1949440 · 36 次调用 → 命中率 95.5% 1053 帧 / 1465 行 / 594.6K 输入 75871 · 输出 44498 · 缓存读 2522880 · 38 次调用 → 命中率 97.1%

观察器版本识别:查看心跳文件中的 usageRate: { minSpanMs, samples, foldThrottleMs }(usageRate)字段(包含 minSpanMs、samples、foldThrottleMs),若缺少该字段说明需重启 DSH Desktop。

客户端 Bundle 包含内容哈希(如 .../client.js&rev=c06568fbaf88308f-47 或 rev=da77570553a2),响应头包含 cache-control: public, max-age=31536000, immutable,修改客户端代码后通常直接刷新页面生效(必要时按 Ctrl+F5 强制刷新)。

独立配色与对比度设计:浅色主题下宿主 --dsw-alias-border-inverted 为 #0000,--dsw-alias-state-warn-primary 为 #f59e0b(对比度仅 2.15:1)。面板采用独立色板,确保达到 WCAG AA 对比度要求:

| 文本类别 | 浅色主题(白底) | 深色主题(卡片 #14171d) |
| 正文 / 次要 / 第三级文本 | 18.5 / 9.7 / 6.4 : 1 | 16.6 / 11.9 / 8.2 : 1 |
| 强调 / 成功状态 | 6.6 / 7.6 : 1 | 8.9 / 10.4 : 1 |
| 告警状态 | 7.0 : 1 | 11.7 : 1 |
| 错误状态 | 7.8 : 1 | 9.0 : 1 |

在浅色主题下,state-warn-primary(即 --dsw-alias-state-warn-primary)默认的 #f59e0b 对比度不足,插件将其重载为 #8a4700(body:not([data-ds-dark-theme]) { --dsw-alias-state-warn-primary: #8a4700 }),调整 -primary 文本与指示点用色(-secondary 与 -tertiary 保持不变,深色主题 body[data-ds-dark-theme] 不受影响),详情页超时行通过 data-tone="err"/"warn" 标记染色。

布局缩放机制:使用 zoom 代替 transform(transform: scale()),使布局与点击区域同步缩放(外层为 .sap-zoom);窗口宽高直接通过 CSS 变量控制(width: var(--sap-w, 300px) 与 height: var(--sap-h, auto))。

数据流与加载机制:面板注册于 shell.overlay 悬浮层,通过读取 projectionValues['dsh-subagent'] 获取数据,包含生效模型参数(model / provider / reasoningEffort),优先读取 state/tasks/<job-id>/meta.json(包含 provider: "my-gateway" 与 model: "deepseek-v4.1-flash"),缺失时回退至 task.json。

安装文件:包含 ~/.dsh/subagent/gui/package.json、gui/lib/index.js、gui/lib/client.js(通过 window.__ModuleLoader__.load 加载,使用 dsh.client 与 exports["./client"]),并在 ~/.dsh/profiles/desktop/cordis.patch.yml 中添加指向 file://(file: / file:/// / file:///…)的 >>> dsh-subagent-panel >>> 受管块。首次使用需刷新页面并重启宿主。

自检脚本: powershell

卡片逻辑(离线:假 window/__ModuleLoader__ + 迷你 React,真跑组件函数)

node $env:USERPROFILE\.dsh\subagent\test\panel-selftest.mjs # 144 项

观察器侧(隔离 DSH_HOME 造假任务现场,投影里带出 model/provider/reasoningEffort/只读转录)

node $env:USERPROFILE\.dsh\subagent\test\observer-selftest.mjs # 50 项

真实任务日志 → token 用量折叠(逐帧解 zstd;只读)

node $env:USERPROFILE\.dsh\subagent\test\usage-fold-probe.mjs --all

启动图(起一个一次性 web 实例,确认插件真的进了 __DSH_BOOT__ 且 bundle 能取到)

dsh --patch $env:USERPROFILE\.dsh\subagent\gui\test-overlay.yml --profile web --no-open --port 34199
node $env:USERPROFILE\.dsh\subagent\test\gui-graph-probe.mjs "http://127.0.0.1:34199/?token=<上面打印的 token>"

6.6 运行中外部会话访问限制说明

宿主在打开会话时会执行 sessions.follow 流处理,对非活跃会话调用 promote() → agents.resume() → persistence.prepare()。若对正在写入的外部会话执行该操作:

1. prepareCore 会合成中断事件(包含 interrupted-tool-result- 与 turn/end{reason:"interrupted"}),并通过 commitRepair 执行文件写入。
2. 随后追加 session/end-seed 并写入未发布后缀。
3. 外部子进程在不知情的情况下继续以相同序号写入,导致日志中出现重复序号(seq)与伪造的 turn/end{interrupted} 标记。

日志取证:分析历史日志中包含此类异常标记的记录(如 session-5e4c139b… 出现重复序号)。此外,follow 循环仅读取宿主自身的 session/event(通过 dsh-api-session-controller),不实时跟踪外部文件写入,且在多写并发时执行 readFile 与全量解压会导致界面卡顿(底层由 SessionHistoryController.follow 处理)。

防护处理策略:

| 任务状态 | 处理方式 |
| 任务已结束 | 点击直接调用常规打开流程 |
| 任务运行中 | 默认仅展示只读详情视图,禁用常规打开,提供需二次确认的强制打开按钮 |
| 实时内容查阅 | 在详情页中只读显示会话转录(观察器折叠会话日志),不打开会话;打开会让宿主接管写权并写坏它的日志 |

6.7 本机子代理与外部 dsh_task 的对比

| 对比维度 | 本机 subagent 工具 | 外部 dsh_task(MCP) |
| 调用来源 | 仅限 DSH 内部运行的模型 | 外部 MCP 客户端工具(Cursor / Claude Code / Codex 等) |
| 运行形式 | 同宿主进程内的子会话(dsh-subagent-spawn-in-process) | 独立子进程(dsh --profile subagent) |
| 递归深度限制 | 受 tool-subagent.maxDepth 限制(默认 3 层) | 由调用方按需控制 |
| 控制通道 | list_agents / send_message / interrupt_agent | dsh_task_status / dsh_health / dsh_task_kill |
| 面板展示 | 「本机 DSH 子代理 ✦」分组(显示 L1/L2/L3 与 ⊞ 标识) | 按外部调用方分组展示 |

卡片同时读取宿主子代理目录(subagentsByParent),调用 ctx.sessions.refreshSubagents 刷新递归层级,对同进程子代理不施加运行中限制。

6.8 任务台账与进程存活状态判定

state/tasks 目录为跨进程共享,当服务进程异常退出时可能残留标记为运行中的失效记录。

系统统一在 taskState、taskState()、listTasks、killByCaller 三处采用统一状态推导逻辑:

1. 当前进程内持有任务句柄:判定为 running。
2. meta.json 中已记录 stopReason:依据元数据转换为终态(ok/error)。
3. 检查进程 PID 是否存活:存活判定为 running,已退出或未记录 PID 则标记为 lost。

listTasks() 返回数据包含以下扩展字段:

| 字段名 | 字段含义 |
| status | 经存活校验后的实际状态(失效记录标记为 lost) |
| stale | 布尔值,true 表示记录标记为运行中但实际进程已退出 |
| pidAlive | PID 存活状态(true / false / null) |
| derived | 布尔值,标记状态是否由 PID 或元数据推导得出 |

dsh_task_kill {caller} 仅对 pidAlive === true 的进程执行终止,将失效记录归入 stale 列表,输出形如 另清理了 N 条残留记录… 的提示信息。旧实现通过 spawn('taskkill', …) 发送终止信号后不校验结果,即使强杀失败也返回 killed: true;当前外部进程终止采用同步执行校验(execFileSync / killTreeSync),确保进程实际退出(返回退出码 0)才报告成功(killed: true),未确认时返回 killed: false, note: "强杀失败:taskkill 未确认终止(退出码 128);进程可能仍在运行,记录保持 running",本进程任务使用 killTree 终止。

自检(使用临时 DSH_HOME 构造测试数据,不修改真实 %USERPROFILE%\.dsh 目录;状态查询调用 taskState() 与 listTasks() 清理标记为 status:"running" 但 PID 已退出的记录;宿主控制器 dsh-api-session-controller 的 summaryFor 基于 running: this.ctx.agents.get(session.id)?.status === "running" 推导): powershell
node $env:USERPROFILE\.dsh\subagent\test\ledger-liveness-probe.mjs # 20 项:残留记录/在跑/meta 推导/真的杀掉/强杀失败如实回报

7. 环境变量一览表

| 环境变量名称 | 默认值 | 功能说明 |
| DSH_SUBAGENT_WAIT_SECONDS | 30 | dsh_task 默认阻塞等待时间(秒) |
| DSH_SUBAGENT_DEADLINE_GRACE | 2 | 截止时间宽限系数:deadlineAt = startedAt + expected_seconds × grace |
| DSH_SUBAGENT_TIMEOUT_SECONDS(或 DSH_SUBAGENT_TASK_TIMEOUT) | 1800 | 最外层绝对超时时间(秒) |
| DSH_SUBAGENT_WATCHDOG_INTERVAL | 30 | 停滞看门狗采样检测周期(秒) |
| DSH_SUBAGENT_STALL_PROBES | 2 | 判定停滞所需的连续无变化采样次数 |
| DSH_SUBAGENT_STALL_MIN_SECONDS | 180 | 判定停滞所需的最小静默秒数 |
| DSH_SUBAGENT_STALL_CPU_MS | 200 | 判定为有进展的进程树 CPU 增量阈值(毫秒) |
| DSH_SUBAGENT_STALL_CPU_WORK_FLOOR_MS | 1500 | 判定为有效工作的 CPU 增长下限值(毫秒,见 §6.2) |
| DSH_SUBAGENT_WATCHDOG | 未设置 | 设置为 off 时整体关闭停滞看门狗 |
| DSH_SUBAGENT_TREE_PROBE_TIMEOUT_MS | 15000 | 进程快照查询单次超时时间(毫秒) |
| DSH_SUBAGENT_PS | 系统 PowerShell | 指定进程快照所使用的 powershell.exe 路径 |
| DSH_SUBAGENT_MAX_CONCURRENCY | 4 | 单个 MCP 服务进程的最大并发任务数 |
| DSH_SUBAGENT_AUTOSTART_MONITOR | 开启 | 设置为 off / 0 / false 禁用自动启动监控窗口(见 §6.4) |
| DSH_SUBAGENT_MONITOR_COOLDOWN_MS | 60000 | 自动启动尝试的冷却时间间隔(毫秒) |
| DSH_SUBAGENT_MONITOR_HEARTBEAT_MS | 20000 | 判定监控宿主离线的超时阈值(毫秒) |
| DSH_SUBAGENT_MONITOR_HOST_CHECK | 开启 | 设置为 off 时关闭宿主已存在则跳过的检查限制 |
| DSH_SUBAGENT_MONITOR_HOST_PROCESS | DSH Desktop.exe | 宿主进程匹配的映像名称 |
| DSH_SUBAGENT_MONITOR_FORCE | 未设置 | 设置为 1 强制跳过宿主检查 |
| DSH_SUBAGENT_MONITOR_CMD | 未设置 | 自定义拉起的可执行文件路径 |
| DSH_SUBAGENT_MONITOR_ARGS | 未设置 | 启动时追加的命令行参数 |
| DSH_SUBAGENT_HOST_PROBE_TIMEOUT_MS | 15000 | 宿主进程查询单次超时时间(毫秒) |
| DSH_SUBAGENT_PERMISSION | workspace-write | 子代理默认权限等级(可用 danger-full-access 放宽、read-only 收紧) |
| DSH_SUBAGENT_TOOLS_MODE | ptc | 叶子进程的工具形态:ptc(程序化工具调用,run_code)或 native;起进程时写成 DSH_TOOLS_MODE 交给 DSH |
| DSH_SUBAGENT_RESULT_CLIP | 60000 | 返回结果的最大字符数截断上限 |
| DSH_SUBAGENT_ACTIVITY_CLIP | 1800 | 状态轮询活动日志的最大字符数上限 |
| DSH_SUBAGENT_OBSERVER_USAGE_REFRESH_MS | 5000 | 单个会话日志重新折叠用量的最小间隔(毫秒) |
| DSH_SUBAGENT_OBSERVER_ACTIVITY_MS | 5000 | 数据变动触发提前重播的最小间隔(毫秒) |
| DSH_SUBAGENT_OBSERVER_RATE_MIN_MS | 3000 | 计算处理速率的最小采样时间窗口(毫秒,见 §6.5.2) |
| DSH_SUBAGENT_OBSERVER_RATE_SAMPLES | 4 | 速率统计平滑处理的采样次数 |
| DSH_SUBAGENT_OBSERVER_TRANSCRIPT_ENTRIES | 60 | 只读预览的会话转录最多保留多少条(从最旧的开始丢弃) |
| DSH_SUBAGENT_OBSERVER_TRANSCRIPT_CHARS | 6000 | 只读预览的会话转录总字数上限(投影要反复重发,必须封顶) |
| DSH_SUBAGENT_OBSERVER_TRANSCRIPT_ENTRY_CHARS | 600 | 只读预览的单条转录字数上限(工具返回体常常上万字) |

CLI 命令行支持参数:--expected-seconds <n>、--acceptance <text>、--caller <name>(默认为 cli)。

8. 常见问题解答

Q: 传入 permission: "workspace-write" 时任务在 2 秒内失败并抛出 permission: composed sandbox and approval defaults match no preset 异常?
问题原因与修复:DSH 内置预设表将 workspace-write 与 read-only 配置为 approval: ask,与无人值守模式的 approval: never 不匹配,导致插件在构造期抛错。Profile 已显式定义完整的权限预设表,为三种模式均配置 never 审批策略,三种权限等级均可正常运行(workspace-write 现为默认值,0.2.1 实测正常)。

Q: 指定 permission: "workspace-write" 时子代理仍可写入工作区外部?
问题原因:全局设置可能覆盖 Profile 的默认预设(config.defaultPreset 与 permission.defaultPreset)。当前版本运行器在执行前会将权限等级写入本次会话的事件流中,确保沙箱策略按当前会话权限执行。可通过 node test/session-perm-probe.mjs <sessionId> 验证实际生效的权限。

Q: 客户端中未显示 dsh 工具?
处理方式:重启对应的客户端工具(Cursor / Claude Code / Codex 仅在启动时加载 MCP 配置)。在 Claude Code 中执行 claude mcp list 验证连接状态(应显示 dsh: … √ Connected),在 Codex 中执行 codex mcp list。

Q: dsh_task 返回 status: running 后的处理流程?
处理方式:在当前会话中调用 dsh_task_status(job_id, wait_seconds=30)(默认等待 30 秒)轮询状态,直至返回终态。返回数据中包含 recent_activity 与 progress_bytes,也可通过查看 prompt.md 与 stderr.log 文件获取实时进度。

Q: 客户端日志中显示 MCP 服务产生警告信息?
技术原因:部分客户端会将 MCP 子进程的标准错误输出一律标记为警告。实测 Cursor mcpprocess.log 记录如下(包含 [warning] [McpProcess stderr] ERR dsh-subagent: MCP stdio server ready …):

[warning] [McpProcess stderr] ERR dsh-subagent: MCP stdio server ready (bridge v…)

当前版本在正常启动路径下实现 正常启动不往 stderr 写任何东西,仅在发生异常时输出。排查问题时可设置环境变量 DSH_SUBAGENT_DEBUG=1 开启详细日志。

Q: 任务返回结果为空?
排查方式:检查任务目录中 meta.json 的 stopReason 与 error 字段,以及 stderr.log 文件末尾输出。若出现 DSH 进程退出码 N,通常为模型配置或认证凭据异常。

Q: 子代理提示命令已执行但未产生实际效果?
排查方式:检查返回内容末尾是否包含 ⚠️ 执行面告警。若存在,说明当前权限等级导致 Shell 命令被沙箱吞没(详见 §5.1),需改用 permission: "danger-full-access" 执行。

Q: 子代理返回文件为乱码或二进制数据?
排查方式:检查该文件是否可由外部进程正常读取,确认运行环境中的文件过滤或拦截策略是否对多进程读写产生影响。

Q: 任务因超时被中断?
排查方式:status: deadline 表示超出预估时间(需调整 expected_seconds 或拆分任务);status: timeout 表示达到外层 timeout_seconds 上限;status: stalled 表示看门狗检测到长时间无进展(详见 §6.2)。同时应确认外部客户端的工具调用超时配置(如 MCP_TOOL_TIMEOUT)。

Q: 如何切换模型?
- 查询可用模型:通过 dsh_health 的 models.providers(即 dsh_health.models)查看当前实例已接入的模型列表(主来源是 DSH 原生登记表,models.source 说明这一次用的是 native / cache / yaml)。
- 编辑策略文件:修改 ~/.dsh/subagent/config/model-policy.md,调整默认模型或预设方案(modelPolicy),修改后即时生效。
- 单次任务指定:在 dsh_task 中传入 model、provider 与 reasoning_effort 参数(如 dsh_task(model: "gemini-3.7-flash", provider: "my-gateway", reasoning_effort: "high"))或通过 CLI 执行 dsh-subagent -m <模型 id>。参数无效时将直接报错(UNKNOWN_MODEL / NO_ADAPTER / UNSUPPORTED_REASONING_EFFORT)。可通过调用 dsh_setup(default_model: "…") 或 dsh_setup(preset: "本项目方案") 修改默认配置。
- 全局修改:在 DSH 桌面设置中修改默认模型。

Q: 外部任务能否递归创建子代理?
不能(参见 §4.1 叶子限制)。外部调用的子代理工具表中已停用 subagent、subagent_fork、workflow、ralph 等分派工具。如需恢复,可修改 profile/cordis.patch.yml 并重新执行 node install.mjs --only profile。

Q: 父任务结束后后台运行的子代理如何查看?
查看方式:DSH 宿主在父会话结束后不会终止后台子代理进程。可在悬浮卡片的「本机 DSH 子代理 ✦」分组中查看带有 L1/L2/L3 标记的子代理运行状态与进度。

9. 目录与文件结构速查

~/.dsh/subagent/
├── README.md 本文档 ├── install.mjs 幂等装配器(--dry-run / --only=…)
├── uninstall.mjs 摘除所有 harness 里的 dsh 注册 ├── profile/ DSH profile 源文件(install 会同步到 $DSH_HOME/profiles/subagent) ├── config/ │ └── model-policy.md 模型策略(用户可以随便改:默认模型 + 预设方案 + 模型选择规则;dsh_setup 落盘到这里) ├── lib/ │ ├── launcher.mjs 定位并解析本机 dsh 启动器(直接 spawn exe,绕开 cmd 转义) │ ├── tasks.mjs 任务生命周期:启动 / 等待 / 查询 / 取消 / 强杀 / 硬截止 / 停滞看门狗 / 并发上限 │ ├── mcp.mjs MCP stdio server 与六个工具的实现在此(工具定义里的委派手册也在这) │ ├── monitor-host.mjs 监控窗口自动拉起(三步判活:心跳+pid 存活 / 残留不采信 / 命令行分类拦第二个 GUI) │ └── util.mjs 路径、JSON、裁剪、进程存活等小工具 ├── bin/ │ ├── dsh-subagent.mjs CLI:任何 harness 都能 shell 调用 │ └── dsh-subagent-mcp.mjs MCP server 入口 ├── test/ │ ├── selftest.mjs 协议级端到端自检(105 项:含模型枚举规则、策略文件链接与预设、首次接入引导块、dsh_setup 落盘/拒绝路径、隔离 DSH_HOME 的 decoy、生命周期留痕) │ ├── monitor-autostart-probe.mjs 监控窗口自动拉起自检(26 项,假 exe + 临时 DSH_HOME) │ ├── ledger-liveness-probe.mjs 台账残留记录自检(20 项,临时 DSH_HOME 造假台账) │ ├── e2e-harness.mjs 验收脚本:让每个 harness 自己委托一次并核对产物 │ ├── concurrency-probe.mjs 并发(N 路同时委托)+ 取消验证 │ ├── tasks-probe.mjs 只验任务层的小烟测 │ ├── session-perm-probe.mjs 解开某个会话日志,打印它实际生效的权限事实 │ ├── panel-selftest.mjs 悬浮卡片逻辑自检(离线 144 项) │ ├── observer-selftest.mjs GUI 观察器自检(50 项,含心跳+规则版本、残留记录收尾、pid 宽限期、token 折叠、吞吐窗口、只读转录、0.1.5 新日志名) │ ├── exec-surface-probe.mjs 执行面自检脚本(11 项:会话日志改名兼容 + 沙箱"空转成功"的判定与零误报) │ ├── live-audit.mjs 活跃审计:真在跑/残留记录/没记 pid/宿主状态/最近任务耗时(--fix 订正残留记录) │ ├── monitor-live-probe.mjs 真心跳 + 真 dsh_task:验 already-running 分支,并确认不重复拉起 GUI │ ├── usage-fold-probe.mjs 真实任务日志 → token 用量折叠(逐帧解 zstd,验底部状态条的数据源) │ ├── leaf-only-probe.mjs 叶子自检脚本(14 项:配置级 7 条检查项 + 会话日志里的真实工具表) │ ├── launcher-heal-probe.mjs 入口自愈自检脚本(13 项:入口失效自愈 / 回退到下一个候选 / 全失效时聚合报错 / app.asar↔app 互换) │ ├── gui-graph-probe.mjs 客户端插件启动图自检脚本(真起一个 web 实例) │ ├── live-probe.mjs 两采样进度自检脚本(判断任务是否真的在动) │ └── dump-session.mjs 解压查看某个 DSH 会话事件时间线 └── state/ ├── tasks/<job-id>/ 每次委托的完整现场 └── selftest-report.json 最近一次自检报告

10. 测试与验收实测数据

端到端测试执行命令:node test/e2e-harness.mjs <工作空间>(依次驱动 Claude Code、Codex、Cursor 分派任务并校验生成结果)。

实测记录(测试工作目录 D:\dsh-subagent-selftest):

| 测试链路 | 调用方式与命令 | 验证结果 |
| Claude Code → mcp__dsh__dsh_task | claude -p … --allowedTools mcp__dsh… | ✅ 6.1s,生成产物 e2e-claude-.txt |
| Claude Code 子代理 → DSH | Task(subagent_type: "dsh") | ✅ 生成产物 subagent-claude-.txt |
| Codex → mcp__dsh__dsh_task | codex exec … | ✅ 12.5s,生成产物 e2e-codex-.txt |
| Cursor → mcp__dsh__dsh_task | cursor-agent -p --force --approve-mcps | ✅ 6.0s,生成产物 e2e-cursor-.txt |
| 命令行工具直接调用 | dsh-subagent -w <dir> "…" / --json | ✅ 5.5s,标准输出返回 DSH 结果 |
| 直接调用 DSH Profile | dsh --profile subagent --prompt-stdin | ✅ 1.2s,退出码 0 |
| 协议层自动化测试 | node test/selftest.mjs | ✅ 54/54 项全绿(涵盖 caller / expected_seconds 必填校验与 isError:true 拒绝、强制终止、失效记录 stale 标记、initialize.instructions、工具定义说明、monitor_host 联动、标准错误静默、版本号一致性及 DSH_SUBAGENT_DEBUG 开关) |
| 并发任务分派 | node test/concurrency-probe.mjs <ws> 3 | ✅ 3 路并发耗时 14.6s 全部成功;取消功能正常 |
| 截止时间保护 | expected_seconds=10,执行 300s 任务 | ✅ 记录 status: "deadline",返回 error: 超出预估时间 10s × grace 1.5 仍未完成,已终止,进程正常终止 |
| 停滞看门狗(真实挂起) | 进程树完全静止(根进程等待 waitpid) | ✅ 判定为 status: "stalled",保存 treePids / treeCpuMs / signalState 取证数据 |
| 停滞看门狗(六进程树挂起) | powershell → bash → bash → node hang.mjs → node(600s sleep),日志 4823B 冻结 122 秒,进程树为 powershell.exe → bash.exe → bash.exe | ✅ 判定为 status: "stalled"(silentProbes: 2、silenceSeconds: 73、treeCpuMs: 1078),通过 CPU 强度下限准确识别空转噪声 |
| 停滞看门狗对照实验(关闭 CPU 强度下限) | 相同六进程树,stderr 自 +24s 起冻在 1439 字节,日志冻结约 590 秒,CPU 增量 cpuDeltaMs 为 0~188ms | ❌ 未能识别停滞:小幅 CPU 增量导致采样计数归零,stalledProbes 循环重置,最终误判为 status:"ok"(退出码 1),验证了设置下限阈值的必要性(见 §6.2) |
| 停滞看门狗(长耗时正常任务) | 连续执行 30 次 Start-Sleep 2,进程树 CPU 持续增加 | ✅ 未被误杀,任务正常返回 status: ok |
| 监控窗口自动启动 | node test/monitor-autostart-probe.mjs | ✅ 27/27 项测试通过:心跳有效且 PID 存活时不重复启动(already-running);心跳过期或 PID 不存在时执行拉起(launched);开关关闭时跳过(skipped);检测到已有图形宿主进程时不重复启动(gui-open-old-observer);真实 --expose-internals 判定为 dsh-cli;FORCE=1 强制启动生效 |
| 监控窗口自动启动(端到端链路) | 真实 MCP 服务调用 dsh_task(HOST_CHECK=off) | ✅ 结果返回 monitor_host: launched(心跳缺失/过期),生成标记文件并在 monitor-host.log 记录 reason=dsh_task <job_id>,任务返回 status: ok |
| 监控窗口自动启动(本地真实环境) | 默认配置运行 | ✅ 心跳文件不存在时检测到已有宿主进程 guiHosts=[45148],状态标记为 state=host-older-observer 与 gui-open-old-observer,避免重复启动窗口 |
| 任务台账状态修正 | node test/ledger-liveness-probe.mjs | ✅ 21/21 项测试通过:失效记录准确标记为 status:lost 与 stale:true;存活任务标记为 running 与 pidAlive:true;killByCaller 仅终止实际存活进程并将失效记录放入 stale 列表,重复执行返回 notFound:true;killTreeSync 对无效 PID 如实报告失败 |
| 悬浮面板底部状态汇总 | node test/panel-selftest.mjs(144 项) | ✅ 格式为 517/12.2K/517K/1.2M;部分命中不四舍五入为 100%;支持宿主投影(tokenUsage)与观察器日志(meta.usage)两种数据源;速率依据观察器 tokensPerSecond 渲染,底部按 tok/s \| 缓存命中 % \| 输入 N tok · 输出 M tok 格式展示 |
| 会话日志 Token 折叠 | node test/usage-fold-probe.mjs --all | ✅ 解析 1282 帧日志(36 次调用,chunk.type=usage 与 data.usage 各 36 次):同轮次替换生效,计算结果为输入 92103、输出 50207、缓存读 1949440、命中率 95.5% |
| 吞吐速率统计逻辑 | node test/observer-selftest.mjs(50 项) | ✅ 2 秒里跳 6000 token 不产生速率,跨 29 秒的 6000 token 准确计算为约 207 tok/s;无输出增量时不显示速率;采样窗口取最近 4 次平均值,日志增长时投影输出 tokensPerSecond 且 < 2000;心跳携带 usageRate 字段 |
| 面板布局与交互测试 | node test/panel-selftest.mjs | ✅ 验证三处调节手柄(data-axis=x,y,xy)、光标样式(cursor: ew-resize/ns-resize)、自定义宽高变量(--sap-w / --sap-h 与 width: var(--sap-w, 300px)、height: var(--sap-h, auto))、多列网格排列(repeat(auto-fill, minmax(132px, 1fr)))、内容滚动样式(.sap-root[data-sized="true"] .sap-body { max-height: none } 与 .sap-root[data-sized="true"] .sap-body)、头部折行样式(flex-wrap: wrap 与 flex: none、white-space: nowrap)及空状态居中样式(justify-content: center 与「无活跃子代理」文案) |
| 启动图与 Bundle 缓存 | node test/gui-graph-probe.mjs <临时实例 URL> | ✅ 10/10 项通过:插件注入 __DSH_BOOT__,包含 sap-grip、auto-fill、flex-wrap、自由缩放、无活跃子代理 等新版代码(旧文案 雷达静默 与旧算法 throughputOf 为 false),响应头包含 cache-control: public, max-age=31536000, immutable |
| 本地台账记录检查 | dsh-subagent --list(即 CLI --list 与 --list 参数) | ✅ 检查 134 条历史记录:所有条目均包含 stale / pidAlive / derived 标记,不存在异常运行中记录(已通过 live-audit --fix 将 5 条修正为 lost) |
| 强制终止功能 | dsh_task_kill {caller} | ✅ 成功终止指定调用方的 2 个运行中任务,重复终止已结束任务返回无需终止提示 |
| 注册状态验证 | claude mcp list / codex mcp list | ✅ 均显示 Connected 或 enabled |
| 宿主界面可见性 | 会话记录位于对应工作空间目录(如 --D-work-demo--) | ✅ 运行中文件持续增长(25秒内由 38KB 增至 89KB),会话在列表中正常展示 |
| DSH 0.1.5-rc.1 适配回归测试 | 7 个自检脚本 | ⚠️ 适配前出现多项测试失败:selftest(43/51)、leaf-only-probe(14/15)、monitor-live-probe(7/8) |
| 修复后回归测试 | 9 个自检脚本 | ✅ 296/296 项全绿:panel 117、selftest 54、observer 35、autostart 26、ledger 20、leaf 14、exec-surface 11、wait-policy 11、monitor-live 8 |
| 启动器入口自愈测试(0.1.6) | node test/launcher-heal-probe.mjs + 10 个测试脚本 | ✅ 309/309 项全绿:覆盖入口路径变更自动修正为 resources\app\lib\desktop-cli.js、首个候选失效自动回退至第二候选、全候选失效聚合报错、app.asar 与 app 路径互换等 13 项新增检查;入口失效导致 Cannot find module 时执行自愈 |
| 动态指定模型与推理强度(0.1.7) | MCP 服务器实测(包含 reasoning_effort=low、错误模型、错误档位、类型校验)+ npm run test:all | ✅ 315/315 项全绿:指定 reasoningEffort:"low" 与 model=deepseek-v4.1-flash 正确传递至任务元数据;指定未知模型返回 UNKNOWN_MODEL(474ms 快速返回且 result.txt 0 字节);指定不支持档位返回 UNSUPPORTED_REASONING_EFFORT(557ms);非字符串类型返回 isError:true 拒绝执行并指明错误参数名;工具结果头部回显 reasoning_effort: <值> |
| 推理强度固定为 high(§3.1) | dsh --profile subagent --reasoning-effort high 测试 | ✅ 声明档位后正常退出并记录 reasoningEffort: "high";网关测试显式 high 消耗 reasoning_tokens=35(未声明时消耗 reasoning_tokens=103);配置错误时直接报错(NO_ADAPTER) |
| 执行等待策略验证(§2.3) | node test/wait-policy-probe.mjs | ✅ 11/11 项通过:默认 defaultWaitSeconds=45 与 statusWaitSeconds=30 且均小于 60;不传 wait_seconds 时短任务在 3.5 秒内直接返回 status: ok 无需轮询;DSH_SUBAGENT_WAIT_SECONDS=5 时超时任务正确返回 status: running 并提示 dsh_task_status(job_id="…", wait_seconds=30) 轮询;dsh_task_status 不传 wait_seconds 时自动等待 20.7s 至终态 |
| 受限模式 Shell 空转测试(§5.1) | 三种权限模式实测对比与日志比对 | ⚠️ 0.1.x 实测:danger-full-access 正常返回命令输出;workspace-write 与 read-only 返回空文本且 isError:false。0.2.1 复测已正常:workspace-write 下 Write-Output SPAWN-PING 返回 "SPAWN-PING\r\n"、退出码 0、sandbox.mode=workspace-write、denied=false(空转检测逻辑保留为兜底) |
| 空转检测准确率 | 真实会话日志执行 detectHollowShellCalls | ✅ 准确检测出 11 次受限空转调用并提取命令内容,全权限日志检测出 0 次,无误报 |
| 会话日志命名兼容性 | node test/exec-surface-probe.mjs + observer-selftest | ✅ 适配 session.jsonl.zstd 命名规则并优先解析 session.v3.jsonl.zstd 格式日志 |
| 请求取消联动测试 | 任务轮询期间发送 notifications/cancelled | ✅ 7/7 项通过:任务转为 cancelled 状态,请求立即返回,子进程实际终止(通过 process.kill(pid,0) 校验),服务正常退出 |
| 桥接层集成测试 | node test/monitor-live-probe.mjs 真实调用 | ✅ 4.9 秒完成,产物正常生成,观察器记录 running=true job=20260911-124736-5c6cbc3c |
| Profile 命令行直接调用 | dsh --profile subagent --prompt "…" | ✅ 1.2 秒完成,返回 stopReason: completed,输出答复 |
| 配置行 ID 兼容性审计 | 检查 app.asar 中的 12 处 Patch 行 ID | ✅ 12 处配置行均存在,已同步 tool-subagent-report(subagent-report)的变更(变更为协议消息 kind) |
| 客户端注入点兼容性审计 | 检查 Web 实例打包脚本(11.2MB) | ✅ 6 处注入点均有效(shell.overlay / subagentsByParent / projectionValues / sessions.open / useSessions / __ModuleLoader__),启动图 10/10,加载卡片版本 36bac599d008e66e-45 |
| 上游 LSP 插件问题影响分析 | 分析 desktop profile --dump-config 与宿主日志 | ✅ 确认桌面宿主不受影响(无 lsp-stdio 与 tool-lsp,无 assertNever 报错),提供 Web Profile 禁用配置 |
| 标准错误输出静默验证 | 验证 Cursor mcpprocess.log | ✅ 正常启动路径下标准错误输出为 0 字节,消除无意义的连接告警;设置 DSH_SUBAGENT_DEBUG=1 时才输出版本横幅 |
| 模型参数限制与校验收敛 | 执行 npm run test:all | ✅ 61/61 项全绿,验证工具描述与参数定义的一致性 |
| 悬浮卡片显示实际生效模型 | node test/observer-selftest.mjs(50 项) + node test/panel-selftest.mjs(144 项) | ✅ 测试全绿(0 ❌):投影优先读取 meta.json 中的实际生效模型,无记录时回退至 task.json;卡片列表行展示模型 chip,详情页展示服务商与推理强度,旧数据兼容显示为默认 |
| 预览窗口只读显示子代理对话 | node test/observer-selftest.mjs(50 项) + node test/panel-selftest.mjs(144 项) | ✅ 测试全绿(0 ❌):观察器与 Token 用量同一趟折叠会话日志,产出「用户 / 助手 / 调用 / 返回」四类转录(推理块不进预览,空返回标成 (空返回)),按 60 条与 6000 字封顶后随投影下发;预览窗只渲染 role 与 text,没有任何写入口,有转录时不再退回 stderr.log 尾部 |
| 动态模型管理策略体系 | npm run test:all(10 个测试脚本,包含 test:all 组合调用) | ✅ 402 项(selftest.mjs 包含 105 条断言);模型列表动态从叶子 profile 的实际生效配置解析($DSH_HOME/profiles/subagent/cordis.patch.yml → $DSH_HOME/cordis.patch.yml → settings.yaml.imported → 旧位置 settings.yaml;流式 models: [ … ] 与块式 models: 下 - id: 两种写法都认,多份合并去重保序),策略文件 config/model-policy.md 支持用户自定义与预设方案(modelPolicy.path / fileUrl / presets),未配置时输出初始化引导,执行 dsh_setup(default_model:) 写入配置后引导自动关闭,非法参数准确返回 isError:true 拒绝处理,短任务正常返回 status=ok |
| 面板不再常驻动画、不再每秒重渲染(0.1.11) | node test/panel-selftest.mjs + 本机 DSH Desktop GPU 进程采样(独立进程,每 5 秒一次) | ✅ 144/144 项全绿(新增 5 条性能防回归断言:扫描光只准挂在 data-busy="true" 规则上、base 的 .sap-card::after 不含 animation、@keyframes sap-sweep 只动 transform 不含 left:、空闲时 .sap-root 为 data-busy="false"、心跳为「有任务 1000 / 空闲 30000」)。修前实测空闲态(无任何任务在跑)宿主 GPU 进程稳定 97%–118% 单核、renderer 仅 12%、Edge 全部进程 0%,证明瓶颈在合成/光栅而非 JS;成因是常驻无限动画 × 设置窗那层全视口 backdrop-filter 每帧重算。0.1.12 起 npm run test:all 402 项全绿;此前唯一未通过的 wait-policy-probe 默认等待断言,根因是叶子 profile 拿不到默认模型导致任务超时,已随 0.1.12 的模型配置注入一并修好 |
| 适配 DSH 2.0.15 / 内核 0.1.7-rc.2(0.1.12) | npm run test:all + dsh --profile subagent --dump-config 生效值 + 真机任务 | ✅ 402 项全绿。①投影块补上 kind(新客户端按它分派,认不出就 assertNever 抛错、整块被丢弃 → 卡片空、⚡ 前缀消失);②模型枚举改读 profile patch 层(0.1.7-rc.2 起 settings.yaml 被一次性导入后改名 settings.yaml.imported),流式与块式两种 YAML 写法都认,迁移前后都枚举得到;③llm-pi-ai 与 agent-default-model 由安装时从本机自己的配置注入叶子 profile(公开仓库不再带内网地址与密钥名),--dump-config 的生效值当场验证,不指定模型的 dsh_task 从必失败变为返回 ok(实测 19.0s 写完文件) |
| 适配内核 0.2.1(0.1.13)+ 发布默认值 | npm run test:all(11 个脚本)+ dsh --profile subagent 真机任务 | ✅ 434 项全绿(selftest 105 / panel 144 / ledger 82 / launcher 26 / leaf 14 / exec 11 / wait 11 / monitor 8 / monitor-autostart 26 / observer / model-catalogue 34)。①凭据:0.2.1 起 apiKeyEnv 先问 credentials 服务,headless 组合把它关掉了 → 叶子读不到 $DSH_HOME/.credentials.yaml,在 profile 里重新启用该行;②模型枚举:subagent-models 在插件树挂载中途就调 llm.listProviders(),0.2.1 下 provider 还没注册完 → 列表为空,改为先 await ctx.get('loader')?.await();③默认模型 deepseek-flash + 推理强度 max:裸 id 在多条路由下登记但档位不同(实测某路由只到 high、另一条支持 max、还有一条支持 max 但余额为 0),叶子按 resolveModelInfo 声明的 reasoningEfforts 选路,并对余额/凭据/档位这类路由级失败自动换下一条(同一次任务实测:首条 QUOTA → 换路由 → ok);④默认权限 workspace-write:实测 Write-Output SPAWN-PING 返回 "SPAWN-PING\r\n"、退出码 0;⑤默认 PTC:起叶子进程时设 DSH_TOOLS_MODE=ptc,实测模型工具清单里 run_code 存在(tool-presentation 行在 profile 组合层不生效,因为它声明的是预设挂载作用域) |
预览分享卡片长什么样 ↓xDJTomato/deepseek-harnessed
分享 · DeepSeek Harness Hub
xDJTomato/deepseek-harnessed

让各类 AI 客户端通过 MCP 调用 DSH 执行任务

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部