DeepSeek Harness 源码解析 · 第 14 讲
DeepSeek Harness 源码解析系列 第 14 讲:Subprocess 子进程管理——spawn、管道、输出收集与终止 基于 DeepSeek Harness 源码 · 2026-08-29 💡 本讲一句话: Agent 每跑一条命令,背后都有一套子进程基础设施在兜底——spawn 时隔离环境、管道收集输出、超时时按 SIGTERM→SIGKILL 升级把整棵进程树干净地收掉。读完你能看懂它怎么防止一个卡死的命令拖死整个 Agent。 一、本讲概览 前几讲我们分析了 Agent 循环、工具注册、系统提示组装、LLM 适配器和 Guard 循环。这些上层组件最终都要调用 Shell 执行命令,而 Shell 命令的执行依赖于一套 子进程管理 基础设施。本讲深入 Harness 最底层的进程控制代码——从 subprocess-local 包的 spawn.ts 、 terminal.ts 和 process-inspector.ts ,到上层的 tool-bash 工具,完整剖析命令是如何被 spawn、输出如何被收集、进程树如何被终止的。 二、模块位置与职责分层 子进程管理分为三层,从低到高: 子进程管理三层架构 🔹 Layer 1 — subprocess-local/spawn.ts :直接调用 Node.js spawn() ,管理 stdio 管道、输出收集器、进程树存活探测与 SIGTERM→SIGKILL 升级 🔹 Layer 2 — subprocess-local/terminal.ts :基于 node-pty 的伪终端(PTY)实现,支持交互式 shell 会话 🔹 Layer 3 — tool-bash/index.ts :面向 LLM 的 bash 工具,封装 foreground/background 两种执行模式,处理沙箱策略与权限升级 三、spawn.ts:子进程创建与输出收集 spawnSubprocess() 是整个子进程管理的入口。它接收一个 SubprocessSpawnSpec 规范,返回一个 LocalSubprocessHandle 句柄。让我们从环境构建开始: 📄 packages/subprocess/subprocess-local/src/spawn.ts(第 37-47 行) export function childEnv(extra?: Readonly<NodeJS.ProcessEnv>): NodeJS.ProcessEnv { const env = scrubbedParentEnv() // 继承父进程环境,但先清除敏感变量(如 token、密码) if (process.platform !== 'win32') return { ...env, ...extra } // Linux/macOS 直接合并,Linux 环境变量区分大小写 // Windows 环境变量不区分大小写,需要手动去重后合并 let entries: [string, string | undefined][] = Object.entries(env) for (const [key, value] of Object.entries(extra ?? {})) { const normalized = key.toUpperCase() entries = entries.filter(([inherited]) => inherited.toUpperCase() !== normalized) // 移除同名旧条目 entries.push([key, value]) // 添加新条目 } return Object.fromEntries(entries) } 设计意图: 子进程的环境不是简单继承父进程,而是先经过 scrubbedParentEnv() 清洗,去除可能泄露的敏感信息。Windows 因为环境变量大小写不敏感,需要特殊的去重合并逻辑。这个细节体现了 Harness 对多平台兼容性的重视。 四、OutputCollector:尾保留输出收集器 这是本讲最核心的设计之一。命令输出可能非常大(编译日志、测试报告等),Harness 不能将所有输出都留在内存…