DSH plugin 的 shell 与子进程 seam:ShellExecRequest 与 DSH_* 环境
DSH plugin 的执行能力分两层:shell seam 面向「跑一条命令」,子进程 seam 面向「起一个进程并接管它的流」。 前者给你 ctx.shell 与 resolve(),把可选字段补全成必填规格;后者给你完全显式的 stdio 处置与受管的 DSH_* 环境。两者共享一套词汇——DSH_* 命名空间、凭据清除与 CollectedOutput 形状都由子进程 seam 拥有,shell 只是重导出。
理解这条分工,才能解释为什么 stdin、env 与 stdoutMaxBytes 只对进程内插件开放,而面向模型的 bash 工具不暴露它们。
DSH plugin 的 ShellExecRequest:请求、规格与前台结果
一句话结论:请求是可选的、规格是必填的;resolve() 是两者之间唯一的转换点,也是上限的唯一来源。
- 请求形态:
ShellExecRequest里只有command必填,workdir、timeoutMs、onExpiry、stdoutMaxBytes都可选。 - 规格形态:
ShellExecSpec的这些字段全部必填,由resolve()按实现配置补全并设上限。 - 超时策略:
onExpiry默认kill;设为none时不启动计时器,timeoutMs只回显进结果。 - 前台结果正交:
exitCode、signal、timedOut、aborted各自独立;timedOut与aborted互斥,按「谁先中断」判定,不会同时为真。 - 输出形状:
stdout/stderr都是CollectedOutput——截断时保留尾部,完整流可落在 spill 文件里。 - 进程句柄不等于后台任务:shell seam 返回进程句柄,后台任务的 id、所有权与控制归 jobs 子系统。
DSH plugin 的 DSH_* 受管环境:为什么不能继承
一句话结论:DSH_* 是 Harness 的事实,不是调用方的输入;实现先清除继承值,再合并本次快照。
- 收集入口:面向模型的 bash 工具通过
ctx.shellEnv收集,再经ShellExecRequest.dshEnv传入。 - 清除顺序:子进程服务在合并当前快照之前移除继承而来的
DSH_*名称。 - 为什么:不可用的当前事实不能继承旧值;调用方
env也不能顶掉受管值。 - 普通环境:
env在凭据清除之后合并;显式的undefinedtombstone 可以删除普通环境里已有的值。 - 验证:在命令里打印一个
DSH_*变量,父进程设过旧值时它也应反映本次快照而不是旧值。
DSH plugin 的沙箱事实:denied 与 runnerFailed 要分开看
一句话结论:denied 是策略拒绝,runnerFailed 是执行器没起来——两者的处置完全不同。
| 字段 | 含义 | 处置 |
|---|---|---|
mode | 命令实际运行的模式 | 判断是否比你预期更宽松 |
denied | 沙箱拒绝了某次文件操作 | 属于策略拒绝 |
enforcement | runner 对模式的执行完整度 | 判断是否降级 |
runnerFailed | 命令运行前 runner 已失败 | 前台抛 SANDBOX_UNAVAILABLE |
- 前台与后台不同:前台在
runnerFailed时抛错;已结束的后台进程只能通过事实通道报告。 - 模式只在拒绝时可见:模型只有在拒绝事实指出生效模式时才知道它。
- 一次性放宽要审批:可用
sandbox_permissions加justification请求严格更宽松的重试,执行前必须经ctx.approval批准该次确切调用。 - 模式只管文件效果:进程约束与文件效果由沙箱 seam 定义,别把模式当成全部隔离。
- 常见误判:命令退出码非零不一定是沙箱拒绝,先看
denied。
DSH plugin 的子进程 seam:stdio 全部显式、argv 不经 shell
一句话结论:没有默认值——每条流的处置、上限与目录都写在 spec 上;argv 绝不经过 shell 解释。
interface SubprocessStdio {
stdin: 'ignore' | 'pipe' | { data: string }
stdout: 'pipe' | 'inherit' | SubprocessCollect
stderr: 'pipe' | 'inherit' | SubprocessCollect
control?: 'pipe'
}
- stdin 三态:
ignore挂到/dev/null,pipe暴露给调用方持续写入,{ data }一次写入并关闭。 - 输出三态:
pipe给原始Readable供协议解码,inherit直通父描述符,SubprocessCollect有界收集。 - 收集与 spill:
maxBytes是内存上限,溢出保留尾部;带spill.maxBytes时完整流可恢复,超出则丢弃不完整的 spill。 - 控制通道:需要独立的字节模式双工时声明
control: 'pipe',不声明就没有。 - 可执行文件查找:
resolveExecutable(command, env?, signal?)校验绝对路径,或经提供方清理后的PATH解析裸名称。 - 结果只承载退出事实:分类与语义由消费方自己判断,seam 不替你下结论。
DSH plugin 的四个消费方:同一 seam 的不同流处置
一句话结论:collect 给批量输出、pipe 给协议分帧、inherit 给诊断直通、终端原语给 PTY。
- bash 执行器家族:收集模式,有界批量输出。
- LSP:原始协议管道,JSON-RPC 分帧。
- PTY 后端:终端进程原语。
- ACP subagent:管道传 ndjson,stderr 用 inherit。
- 选型判据:要结构化解析用
pipe;要给人看诊断用inherit;要展示结果用 collect。 - 诊断尾部可以不留文件:不需要完整流时省略
spill,只在内存里保留尾部。
DSH plugin 排查注意事项
一句话结论:执行问题先分「规格没补全」「环境不对」「沙箱拒绝」「流处置选错」四类。
- 字段缺失报错 → 没走
resolve(),直接把请求当规格用了。 DSH_*是旧值 → 别从父进程继承,走dshEnv快照。- 命令被拒 → 看
denied与mode,不是先怀疑命令写错。 - 前台抛
SANDBOX_UNAVAILABLE→runnerFailed,runner 没起来。 - 输出丢头 → 截断保留的是尾部,需要完整内容就开 spill。
- 修复状态如实:本文的请求/规格拆分、
DSH_*规则、沙箱事实与 stdio 处置均取自官方「Shell 执行器」「子进程」「沙箱」页面;接口签名以对应 Cordis API 与你所用版本为准。
常见问题
DSH plugin 的 shell seam 把面向模型的请求与执行器使用的规格分开:请求里的 workdir、timeoutMs 可选,resolve 会按实现配置补全并设上限。工具层必须先 resolve,拿到的必填规格才是真正执行的值。
DSH plugin 的 DSH_* 是 Harness 拥有的子进程事实,实现会先丢弃环境中已有的 DSH_* 再合并本次快照。否则一个已不可用的事实会继承到旧值,调用方也无法用 env 覆盖受管值。
DSH plugin 的 denied 表示沙箱拒绝了某次文件操作,runnerFailed 表示命令还没跑 runner 就失败了。前台执行在 runnerFailed 时抛 SANDBOX_UNAVAILABLE,后台进程只能通过事实通道报告。
DSH plugin 的 CollectedOutput 在截断时 text 保留流的尾部,完整流写入私有文件并通过 spillPath 给出路径。是否保留 spill 文件由消费方选择,诊断尾部可以只在内存里缓冲。
不会,DSH plugin 的子进程 seam 不应用任何默认值:stdin、stdout、stderr 的处置方式、上限与目录全部显式给出。argv 也绝不经过 shell 解释,避免隐式解析带来的注入面。
相关术语
- ShellExecRequest(执行请求)
- ShellExecRequest 是面向模型或插件的执行请求,command 必填,workdir、timeoutMs、onExpiry、stdoutMaxBytes 等可选。它必须先经 resolve 补全为 ShellExecSpec 才能执行。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/shell
- CollectedOutput(收集输出)
- CollectedOutput 是一条被收集的输出流:文本在被截断时保留尾部,truncated 标记是否丢字节,spillPath 指向保存完整流的文件。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/subprocess
- DshEnvironment(受管环境)
- DshEnvironment 是一次子进程执行中由 Harness 拥有的 DSH_* 变量集合,键受命名空间约束。实现先清除继承的 DSH_* 再合并这份快照。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/subprocess
- SubprocessCollect(有界收集)
- SubprocessCollect 是子进程输出的一种处置方式:在内存里按 maxBytes 有界收集,溢出保留尾部,并可选择把完整流写入带上限的 spill 文件。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/subprocess
来源
- DeepSeek Harness 文档 — Shell 执行器(请求/规格、前台结果、沙箱事实、后台进程)· deepseek-harness.github.io
- DeepSeek Harness 文档 — 子进程(DSH_* 命名空间、stdio 处置、spawn spec)· deepseek-harness.github.io
- DeepSeek Harness 文档 — 沙箱(模式、强制执行与故障关闭)· deepseek-harness.github.io