OSC 7501 提案解读,让程序把自己的状态告诉终端

OSC 7501 是一套让程序主动向终端报告自身状态的转义序列提案。程序通过本来就有的 pty 发出序列,声明自己处于空闲、运行、完成、阻塞还是失败,阻塞时用一个字段说明卡在权限确认、提问还是认证,一行人类可读的原因用 base64 携带。终端收到后自行决定怎么展示。提案 2026 年 10 月公开,作者已经实现过两遍。

下面面向终端模拟器开发者、命令行工具作者和在做编码代理的团队。字段可以确认,落地范围取决于终端厂商接不接。

长任务在终端里为什么一直是盲区

构建、部署、包升级、编码代理都在自行工作、等待用户、结束之间来回切。用户按下回车去做别的事,回来只看到最后一屏输出。旧办法是盯着前台进程,一变就发通知,或者等输出安静一段时间再提醒。安静判定要挑阈值,编译中间的停顿和跑完的样子一样。

编码代理把这件事放大了。有人同时开十几个代理,每个跑一阵就停下来要权限或者报完成。

退出码和进度条各漏掉了什么

退出码只在进程结束的那一刻产生,而且只有一个数。任务跑了四十分钟卡在确认提示上,还没有退出码,终端只知道进程还活着。进度条假定任务在推进,表达不了停下来等人的状态。

窗口标题加屏幕内容推断是第三条路,靠正则匹配。公开例子里有一份代理状态检测规则,给一个代理写了 16 条,三个月里改了十次。

一条序列怎么表达被权限确认卡住

序列体是一串键值对,只有 state 是必填项,取值有五个。

  • idle 表示空闲,等待用户的下一条指令。
  • working 表示正在运行,可以带一个进度百分比。
  • done 表示已经完成,结果就绪但用户还没看到。
  • blocked 表示无法继续,要等用户做点什么,kind 说明是哪一类,取值有 permission、question、auth。
  • error 表示失败并停止。

可选的 app 是机器可读的程序名,msg 是一行人类可读文本。部署工具卡在等生产环境审批就是 state=blocked 配 kind=permission。

ESC ] 7501 ; state=blocked:kind=permission:app=terraform:msg=... ESC \

同时跑多件事的程序可以报多条记录,用层级 id 区分,比如根上报运行中,us-east 推到 40%,eu-west 卡在等审批。状态清空后记录删除。

msg 为什么用 base64

msg 是唯一携带自由文本的字段。终端要把这行字塞进通知或者状态栏,这行文本里可能出现分隔符、反斜杠、控制字符,甚至另一条转义序列,解析器得先做转义和边界判断。base64 之后字符集固定,序列边界清楚,代价是消息不再肉眼可读。

终端和工具两侧各自要做什么

  • 程序侧发一条序列就够。POSIX sh 就能写,不用 SDK,不用 socket,不用环境变量。
  • 终端侧要解析这个序列,处理状态记录的建立、更新和清除,再决定怎么呈现。
  • 工具链侧在自己的状态切换点各发一条。
status() {
  printf '\e]7501;state=%s:msg=%s\e\\' "$1" "$(printf '%s' "$2" | base64 | tr -d '\n')"
}
status working "Syncing photos"
rsync -a ~/Photos backup:/photos && status done "Photos synced" || status error "rsync failed"

提案阶段要盯的几处风险

  • 终端支持度。目前只有少数实现认识这个序列,发给不支持的终端等于没发。
  • 兼容性。提案能安全铺开的前提是终端忽略不认识的 OSC,这条约定不成立时内容会被打印到屏幕上。
  • 状态由程序自报,误报没法自动纠正,切换太频繁还会把提醒变成噪音。

现在该不该接入

  • 在做编码代理或长任务工具的团队,值得先把序列发出去,成本十几行。
  • 做终端模拟器或多路复用器的团队,解析和呈现是必做项,早参与能影响规范定型。
  • 做汇总视图工具的团队,协议能省掉一部分规则维护,现阶段仍要保留兜底。
  • 只跑几秒钟短命令的脚本,不值得加。

常见问题

需要额外服务吗。不需要,序列走 pty,SSH 和容器里同样可用。

一个程序能报多条状态吗。可以,用层级 id 区分。