DeepSeek Harness 的 terminal 工具:README 说 pty,代码是 terminals
翻 deepseek-harness 的时候,如果你想搞清楚某个插件到底依赖哪几个 service,最直接的办法是看它的 inject 数组。这个数组决定了插件启动时会从上下文里拿到哪些能力,也是读这个仓库最省事的一条线索——顺着 inject 就能知道这个包挂在哪几个 seam 上。
问题是,packages/terminal/terminal-bash/ 这个包,README 和代码在这一处对不上。
先把话说在前面:这个仓库建立于 2026-08-13,我们采集事实的时间是 2026-08-16,前后只差三天;根 package.json 里的 version 是 0.1.0-rc.5,GitHub 上一个 Release 都没有,README 自述处于开发者预览阶段,并明写未来会出现破坏兼容性的变更。所以下面提到的所有名字、行号、默认值都可能随时变,请以仓库当前内容为准。
两处原文,并列摆着
一处在文档。packages/terminal/terminal-bash/README.md 第 9 行的「Plugin (terminal-bash)」小节,开头一句写的是:
The plugin injects
pty,sandboxPolicy, andsubprocess
同一个包的中文版 README.zh.md 第 9 行是对应的中文表述,注入的三项同样写作 pty、sandboxPolicy、subprocess。
另一处在代码。packages/terminal/terminal-bash/src/index.ts 第 25 行:
export const inject = ['terminals', 'sandboxPolicy', 'subprocess']
后两项一致,第一项一个写 pty,一个写 terminals。两者不一致,以我们实读的仓库状态为准。
按照本站写这类差异的规矩,说到这里就停:我们不去猜哪个是「对的」,不去猜是谁没同步,也不拿这一处去评价项目质量或团队水平。它就是一条你在读代码时需要知道的事实——照 README 的名字去找服务,会找不到。
补一句我们核过的边界:在 packages/ 下的 .ts 文件里,我们没有搜到 ctx.pty 这个服务。唯一命中 pty 的地方是 e2b 的一个测试文件 packages/e2b/subprocess-e2b/tests/terminal.spec.ts 第 91 行,那里的 Sandbox['pty'] 是 E2B SDK 自己的类型,和 dsh 的 seam 命名不是一回事。
自己动手核,两分钟的事
这类差异最好的处理方式不是记住结论,而是记住怎么核。给三个可执行的判定动作:
第一步,直接对照两行。 打开 packages/terminal/terminal-bash/README.md 看第 9 行,再打开 packages/terminal/terminal-bash/src/index.ts 看第 25 行。两行摆在一起,差异一眼可见。这是最不容易出错的做法,因为不涉及任何工具行为。
第二步,用文本检索把仓库里所有 inject 声明拉出来。 Linux / macOS 侧:
grep -rn "export const inject" packages/
Windows 的 PowerShell 侧:
Select-String -Path packages\*\*\src\index.ts -Pattern "export const inject"
以上两条只是通用的文本检索命令,不是这个项目提供的命令,我们也没有在本机执行过,实际写法请按你自己的环境调整。
第三步,反过来查服务名有没有对应实现。 如果 README 里出现一个你没见过的 service 名,直接在 packages/ 下搜 ctx.<名字>,搜不到就说明它在代码里没有对应的注入点。ctx.pty 就属于这一类。
顺手说清 ctx.terminals 这一层是干什么的
既然名字是争议点,把这一层的实际形状摆出来会更清楚。
packages/terminal/ 下截至 2026-08-16 有三个包:@deepseek-ai/dsh-terminal、@deepseek-ai/dsh-terminal-bash、@deepseek-ai/dsh-tool-terminal。整个目录 src/ 下有 11 个 .ts 文件、2306 行——想通读一遍成本并不高。
ctx.terminals 提供的是持久 PTY 会话,也就是能跨多次工具调用保持状态的终端。模型面向的工具一共 6 个:terminal_open、terminal_send、terminal_read、terminal_signal、terminal_close、terminal_list,都在 packages/terminal/tool-terminal/src/index.ts 里注册。
而 terminal-bash 是挂在 ctx.terminals 上的一个后端实现。它的配置默认值在 packages/terminal/terminal-bash/src/config.ts 第 44 到 59 行,其中和「它到底会在你机器上起什么」直接相关的三项是:
| 字段 | 默认值 |
|---|---|
shellPath | '/bin/bash' |
shellArgs | ['--noprofile','--norc','-i'] |
timeoutMs | 30_000 |
这些是配置里写死的默认值,不是运行表现的承诺,我们也没有运行过它,所以不谈它跑起来是什么样。要强调的是另一件事:terminal-bash 默认会在本机起一个交互式 shell,并让它跨多次工具调用保持存活。这一层就是「真的在你机器上执行外部程序」的地方,不是比喻。
沙箱这边的口径同样值得记一下(来自 packages/terminal/terminal-bash/README.md 第 9 行):danger-full-access 模式下直接起 shell、不要求沙箱 provider;受限模式则要求同世界的 ctx.sandbox 并把 shell 的 argv 包起来,没有就在 spawn 前失败。另外,当某个 owner 有开着的 PTY 或正在 spawn 时,切换到不同实际模式的变更会在 sandbox/mode 事件提交前被拒绝,README 给的理由是不让一个以更宽权限打开的终端在降级后继续存活。
这不是孤例,同仓还有几处同类差异
如果你打算靠 README 的 inject 行来理解这个仓库的依赖关系,下面几处最好一起知道,免得踩第二次。这里只并列位置与原文,同样不推断原因:
packages/shell/tool-bash/README.md第 7 行写的是inject: ['tools', 'bash', 'systemPrompt', 'bashEnv'],而代码packages/shell/tool-bash/src/index.ts第 31 行是['tools', 'shell', 'systemPrompt', 'shellEnv'];中文版README.zh.md第 7 行同样写bash/bashEnv。packages/shell/tool-pwsh/README.md与README.zh.md的第 7 行同样写['tools', 'bash', 'systemPrompt', 'bashEnv'],代码packages/shell/tool-pwsh/src/index.ts第 49 行是['tools', 'shell', 'systemPrompt', 'shellEnv']。- 生成的配置目录中英两版也不一致:
docs/config-catalog.md第 2341 行与第 2506 行写的是tools·shell·systemPrompt·shellEnv,与代码一致;而docs/config-catalog.zh.md第 2343 行与第 2508 行写的是tools·bash·systemPrompt·bashEnv。我们用 Python 统计整个文件,bashEnv在英文版出现 0 次、在中文版出现 2 次。 - 名字层面还有一处:
packages/terminal/README.md第 9 行的包表,第一行把这个包写作pty并链到terminal/README.md,而磁盘上的目录名是terminal/terminal、包名是@deepseek-ai/dsh-terminal。同类情况在packages/code-runtime/README.md也有一处,表里写的是code-runtime-worker/,目录名是code-runtime-worker-thread。
把这几条放在一起看,能得出的操作性结论只有一条:在这个仓库里,服务名、包名、目录名三者要分别核,不能互相推。 想知道注入什么,看 src/index.ts 的 inject;想知道包名,看该目录的 package.json 的 name;想知道路径,看目录本身。
什么情况说明你遇到的不是这个问题
排查文章最该写清楚的是边界,所以这一段不能省。以下几种情况,和本文说的这处差异无关:
- 你报的错里出现的是
sandboxPolicy或subprocess。 这两项在 README 和代码里是一致的,问题不在名字对不上这一层。 - 你在 e2b 相关代码里看到
pty。 那是 E2B SDK 自己的类型(packages/e2b/subprocess-e2b/tests/terminal.spec.ts第 91 行),不是 dsh 的 service 名。顺带一提,e2b 家族在packages/e2b/README.md第 5 行被自述为一个 experimental 的 provider-composition POC。 - 你的问题出在受限模式下没有挂
ctx.sandbox。 按 README 的说法,那种情况是在 spawn 前就失败,属于沙箱 provider 是否挂载的问题,和注入项名字无关。 - 你读的仓库版本和我们的快照不同。 我们对着的是 2026-08-16 取的快照
47f9438。这个仓库处于开发者预览阶段、README 明写会有破坏性变更,上面所有行号和名字都可能已经变了——差异是否还在,请以你手上那份为准。
最后重复一句本文的立场:以上全部是文档与源码的静态阅读结果。我们没有安装 dsh,没有起过任何 PTY,没有跑过任何一条命令,所以不谈它跑起来是什么样、快不快、稳不稳。这一层会在本机执行外部程序,是否使用、怎么隔离,请结合你自己的环境评估。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 的文件搜索:打包 ripgrep 与 unconfined spawn
- DeepSeek Harness 的隔离边界:仓库三处明写「这不是安全边界」
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。