ByteNoteByteNote
DeepSeek Harness 持久 PTY 会话解析
字

字节笔记本

2026年10月6日 · 约 5 分钟读完

DeepSeek Harness 持久 PTY 会话解析

API中转
¥120

DeepSeek Harness(简称 dsh)是 DeepSeek 开源的智能体框架,主打"一切皆插件",由 Cordis 框架驱动,采用 MIT 协议,目前处于开发者预览阶段。大多数框架把"执行命令"做成一次性调用,命令结束,现场就没了;dsh 的终端子系统走了另一条路:把交互式终端做成可持久化的 PTY 会话,模型可以打开一个 shell 后反复读写,跨多轮任务保持现场。本文基于它的终端子系统文档,拆解这套持久 PTY 会话的类型设计。

身份:服务铸造,授权认人不认名字

每个会话有一个 TerminalSessionId,由服务统一铸造,而不是调用方自己起名。可选的名称只是拥有者本地的显示元数据;鉴权时比较的是拥有该会话的确切 Agent,既不看名称,也不接受猜测的 id。这条规则把"谁能碰这个会话"收敛成一个明确事实:只有主人本身。

与状态相关的还有两个互相独立的维度。TerminalWaitReason 说明一次发送为何把控制权还给调用方,取值有 stdin_read、inferred_idle、timeout、session_exit 四种;TerminalSessionStatus 只有 running 与 exited 两种,后者附带退出码与信号。关键区别在于:静默或超时返回时,顶层 shell 很可能还活着;只有 session_exit 才代表顶层 shell 退出,而且是顶层 shell,不是任意一个前台子进程退出。

后端:可替换的启动契约

TerminalBackend 是 PTY 会话类型的可替换提供方,一个稳定的 type 字段标识一种会话类型,spawn 负责把会话启动起来并探测就绪。服务端的纪律很严格:TerminalSessionService 只在初始化成功后才对外发布这个会话,发布之后由它统一负责 id 授权与清理。

启动失败的处理同样讲究。后端如果在半途失败,必须先清理已经创建的部分资源;如果连清理都失败了,就用 TerminalBackendCleanupError 拒绝,这样资源释放流程既能保留清理失败这个事实,又不会用它覆盖调用方原本的取消原因。会话发布后,TerminalBackendSession 持有终端状态:motd 是打开会话时返回的首屏有界输出,pid 是顶层进程号,可能缺省;close 负责幂等地关掉整棵进程树,并等待彻底停稳。

发送:一次只放行一个

一个活跃会话同一时刻只接受一个活动发送。TerminalSendOperation 暴露三件事:done 是一个 Promise,在就绪、超时、取消或顶层进程退出时落定;readOutput 是一个读取后即推进的输出游标,通用后台任务靠它增量消费输出;cancel 请求 SIGINT,操作落定之后再调只会返回 false。

落定结果是 TerminalSendResult:viewport 是返回时刻剩余的有界渲染增量,waitReason 回答为什么此刻返回,sessionStatus 报告顶层会话状态,truncated 标记输出是否被丢弃。与发送通道分开的还有一条读路径:read 从保留的 scrollback 里按页取历史输出,支持从最新位置起算的相对偏移,每一页都有界。

归属与持久性:重载不丢现场

TerminalSessionService 把一项等待完成的清理挂在确切拥有者的作用域上,外来拥有者的操作一律拒绝;后端或工具插件重载时,会话保持存活,不跟着重启。另一条底线是持久化不另起炉灶:PTY 状态与原始字节始终留在进程内,真正被记录的是模型输入与有界返回输出,它们经由现有的 tool/call、tool/result 与任务结果路径落库,没有第二套 PTY 会话事件。

ctx.terminals:模型与插件看到的面

这套能力通过 ctx.terminals 暴露,接口面有九个方法。registerBackend 注册一种后端类型,返回精确移除这一贡献的 disposer;listBackends 按注册顺序列出类型名;spawn 为确切拥有者创建并发布会话,取消信号只作用于未发布的初始化阶段;hasOwnerActivity 判断一个拥有者名下有没有已发布或正在创建的会话,覆盖从 spawn 到 close 的整个区间,中间没有发布空档;startSend 发起互斥的交互发送;read 读回滚缓冲;signal 向已验证的前台进程组投递被允许的 POSIX 信号;kill 幂等关闭会话,真正关掉才返回 true,同一关闭已在途时返回 false;list 按发布顺序返回该拥有者名下的会话快照。

对 agent 框架来说,这套设计把"终端"从一次性命令执行升级成可以反复读写的长会话:身份、授权、互斥、有界输出与清理各有明确契约。插件作者按 TerminalBackend 接入自己的会话类型,就能复用同一套服务端语义。想动手试的话,用 npx @deepseek-ai/dsh web 即可把框架跑起来。

相关文章

分享: