Skip to content

Agent 状态 ​

文档编写中

部分内容可能尚不完整。

Claude Code 会话旁边的 Agents 面板:运行中和空闲的 Agent、子任务,以及一个 Agent 的悬停卡片

跑着长任务的 Thread 会带一个实时状态,让你一眼看出哪些会话需要你,而不必逐个访问。当一个 Thread 结束一段工作、或者开始等待一个决定时,会播放一声短提示音。

状态分为 working(工作中)、blocked(等待用户决定)和 idle(空闲);上报自己因错误停下的程序,还有 error。ThinkTerm 无法归类的会话则不做标记。

该检测默认开启,可以在 设置 › 侧栏与插件 里关掉 —— Agents 面板和检测器共用同一个开关。在配置文件里写 agent_status_detection = false 也能关掉它;mux server 没有设置窗口,只看这个配置项。关掉之后,只有下面讲到的原生转义序列还会被读取。检测在 pane 所在的那一端运行,因此在 mux server 上关掉它,和在你面前的客户端上关掉它,是两件事。

支持的 Agent ​

下面这些是 ThinkTerm 叫得出名字的 Agent。发送状态上报的 Agent 会自己把状态告诉 ThinkTerm,上报直接决定状态;不发的,ThinkTerm 就读屏幕规则。检测它们不需要往其中任何一个里面安装东西 —— 没有 hook、没有插件、没有包装脚本 —— 也不需要对它们做任何配置。另外,只有在你自己运行时,thinkterm skill setup 才会给 Claude Code 和 Codex 加上一个 skill 和一个会话启动 hook,让它们能使用其他 pane;见 命令行。

Agent在 PATH 中查找的命令覆盖方式
Claude Codeclaude2.1.295 及以后的版本用状态上报;更旧的版本用屏幕规则
Codexcodex0.163.0 及以后的版本用状态上报;更旧的版本用屏幕规则
Copilot CLIcopilot屏幕规则
Cursor Agentcursor-agent 或 cursor屏幕规则
Pipi1.1.0 及以后的版本用状态上报;更旧的版本用屏幕规则
OpenCodeopencode屏幕规则
Kimi Codekimi屏幕规则
Soulsoul通过 THINKTERM_AGENT 自行上报状态
OMPomp已列出,但还不做检测:它照常运行,只是不显示状态

Claude Code 启动时会问终端是否支持状态上报,ThinkTerm 会回答,所以什么都不用设置。在 tmux 里、在 Claude Code 自己的后台会话里,或者设了 CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 时,它不会发上报,这时照旧由屏幕规则覆盖。Codex 从 0.163.0 开始,只要输出是终端就会发送,不先询问。Pi 从 1.1.0 开始发送,可以用 PI_PROGRAM_STATUS=1 或 0 强制打开或关闭。用到屏幕规则时,它们读取 pane 实时屏幕的底部和终端标题,认出是在工作、在等你输入,还是空闲。其他程序要加入这份名单,可以自行上报状态,或者为它写一份规则文件。

设置 › Agent 在 集成 下列出同样这些 Agent,每个带着它的标志,和一行说明它是怎么被覆盖的。对屏幕规则类的 Agent,这一行还会说明是否在你的 PATH 里找到了它的命令;ThinkTerm 除了自己的 PATH,也会看你登录 shell 的 PATH,再加上常见的安装目录。这只是给你的提示,不是前提:不管这项检查找没找到,检测都能认出正在运行的程序。点一行会展开一段简短说明,其中包括覆盖它的规则文件该放在哪个路径。

状态是怎么判定的 ​

发送自己状态上报的程序,会替它所在的 pane 给出答案:状态由上报决定,上报有效期间,这个 pane 不读任何屏幕规则。上报里的 app 就是 Agent 的名字,所以 ThinkTerm 从没听说过的程序,也会以自己的名字出现在 Agents 面板里。上报代表发出它的程序,以及这个程序把终端交给的程序,比如分页器或编辑器。shell 一拿回终端,进行中的工作上报就结束了;done 和 error 的结果会一直留着,直到有人看过这个 pane。

对不发这种上报的程序,会参考三类证据,契约性由强到弱。

1. THINKTERM_AGENT 变量。 程序自行上报身份、会话和状态。这是下文描述的 ThinkTerm 自己的变量;和屏幕规则不同,程序界面怎么重新设计它都不受影响。

2. 屏幕规则。 用 TOML 清单匹配 pane 的可见输出及其终端标题。内置规则来自 herdr 项目。

3. 原生转义序列。 OSC 9;4 进度报告,以及标题开头的 spinner 标记。功能关闭时,也只会用到这一类。

有两条仲裁规则很关键,而且它们是有意指向相反方向的:

  • 活着的进程在"身份"上胜出。 匹配到清单的前台进程,是"现在正在运行什么"的事实依据。上报的变量是一条来自过去的消息,永远不能覆盖它 —— 因此一个已退出程序留下的变量,无法给之后在该 pane 里运行的东西贴错标签。
  • 屏幕在"状态"上胜出。 程序普遍会漏掉"用户按了 Esc"和"提示被取消"这两种转换,而它们最终都表现为屏幕上明显空闲。因此匹配到的屏幕规则的优先级高于上报的状态。

由屏幕推断出的转换 —— 离开 blocked,以及从 working 转为 idle —— 会有约 450 毫秒的去抖,因为一帧画到一半不该触发一次通知重放。通过协议上报的状态则跳过去抖:明确的上报不是猜测。

在侧边栏里 ​

侧边栏里 Thread 的那一行,为整个 Thread 带一个标记,不管它有几个 pane。这个标记由 Thread 里的每个 pane 共同决定:有 Agent 状态的 pane 用 Agent 状态,其他 pane 用原生转义序列:

Thread什么时候
需要关注任一 pane 处于 blocked,或者通过 OSC 9;4 或状态上报报告了错误。它压过其他一切。程序因错误停下的 Thread,标的是红色叉号,而不是表示等待的琥珀色
运行中任一 pane 处于 working,或者显示着进度报告或标题 spinner
已完成Thread 跑过、现在停了,而你从那以后还没看过它
空闲以上都不是。这一行不带标记

侧边栏的 已完成 不是上报来的状态,而是 ThinkTerm 自己的记录:表示一个 Thread 在你注意力不在它身上时,从工作转为了空闲。它一直保持到你打开这个 Thread,或者在里面打字、点击为止,重启之后也还在。需要关注和已完成的 Thread 还会汇总到侧边栏的「通知」按钮下,跨越所有 Space —— 见 Space、Project 与 Thread。

把指针停在某个 Thread 的那一行上,旁边会弹出一张卡片。卡片写着 Thread 的名字和其中 Agent 的数量,然后逐个列出有状态的 Agent:标志、标题和状态图标,下面是用文字写出的状态,以及程序说了的话 —— 进行到哪、在等什么、正在做什么。窗口有多高,卡片就列出多少个,列不下时以一行 还有 3 个 收尾。在标签栏里,工作中的 Agent 会让标签图标转起来;在等你、因错误停下、或者手里有个还没人看过的结果的,会在图标角上加一个点。结果在它所在的 pane 出现在一个有焦点的窗口前面时,就算看过了。

Agents 面板 ​

右侧栏里的 Agents 面板,列出每一个运行着已识别 Agent 的 pane,覆盖所有 Space 和所有窗口,也包括你所连服务器上的 pane。窗口的右侧栏默认打开在这个面板上;面板关掉时,则打开在仍然提供的第一个面板上。

每个 Agent 是一张卡片。第一行是 Agent 的标志、pane 的标题 —— fix the build —— 以及右端表示状态的图标:工作中是转动的 spinner,在等你是琥珀色的警示,出错是红色叉号,还没人看过的结果是绿色对勾,空闲是灰色对勾。没有自己标志的 Agent,比如 Soul,或者由规则文件加进来的,会用一个通用标志,并在标题前写上它的名字:Soul · fix the build。第二行是 pane 所在的位置,my-project · Thread 2。图标已经说明了状态,所以这一行只在图标说不清的时候才用文字写出状态:空闲 的对勾和结果的对勾只差颜色,以及没有图标的 未知:空闲 · my-project · Thread 2。

对发送状态上报的程序,卡片还会带上它说的内容:位置上面一行写它在做什么、做完了什么、或者为什么停下,它在等什么 —— 要权限、要回答 或 要登录 —— 以小标签的形式放在这一行前面;一条进度条;以及它的子任务,折叠成一行计数,点一下展开成列表。展开的列表里,每个子任务前有一个状态颜色的圆点,先列出前五个,最后以一行 还有 3 个 收尾。设置 › Agent › 状态显示 里四项各有一个开关,默认都开着,下面的视图菜单切换的是同样这几个。Thread 的悬停卡片和通知里写什么,也由它们决定。工作中的说明会有流光掠过,出错的说明是红色。太长放不下的行不换行,而是在末尾淡出;说明里的路径如果这样就能放下,会先被截成最后几层目录。

pane 位于这台电脑上某个已链接 git worktree 里的 Agent,位置下面还有一行,带着分支图标:worktree 的文件夹和在那里检出的分支,feature-login · feat/login;没有分支时是提交的开头几位。Thread 是按任务命名的,几个 Thread 可以同名而检出目录不同。仓库自己的检出什么都不显示,因为 Thread 已经写明了它的 Project;另一台机器上的 pane,或者 ssh 会话里的 pane,也不显示。

本窗口活动 pane 里的那个 Agent,卡片比面板底色亮一档,像浮起来一样,让你从列表里看出自己在哪。

分组与视图菜单 ​

卡片归在带数量的标题下,默认按状态分组,顺序就是它们需要你的程度:需要关注、运行中、已完成、空闲,以及状态判断不出来的 Agent 所在的 未知。组内按 Project 和 Thread 排序,再按 Agent,所以切换 Space、或者某个 Agent 状态变化时,列表不会重新洗牌。

右上角的按钮打开视图菜单,悬停提示是 视图选项。它左边那一行,没话说时是空的:重新加载后的几秒里显示 检测规则已重载。,或者有几份文件被拒绝;列表为空时显示 未检测到 Agent。

条目作用
显示勾选要列出的状态:运行中、需要关注、已完成、空闲。最后一个仍在显示的不能被隐藏
机器勾选要列出其 Agent 的机器:本机 和每台服务器。只在 Agent 分布在多台机器上、或者隐藏了某台机器时才出现
分组方式状态(默认);会话,以 Project 和 Thread 为标题,没有 Thread 的 Agent 归入 其他;或 不分组
排序方式固定顺序(默认),组内按 Thread、Agent、pane 排;或 最近活动,把最近一次状态变化的 Agent 排在最前
说明、等待原因、进度、子任务设置 › Agent › 状态显示 里的那四个开关
重新加载检测规则重新加载屏幕规则,在你添加或修改了覆盖文件之后用。按钮旁边那一行随后显示结果;被拒绝文件的原因写在日志里

这些选择会被保存,并作用于所有窗口。如果选择把现有的 Agent 全部隐藏了,面板会显示 显示选项隐藏了所有 Agent。无论在哪一处关掉检测,这个面板都会从侧栏里拿掉。

点一张卡片会带你到那个 pane。在本窗口里的 pane,会选中它的标签和这个 pane。否则 ThinkTerm 会打开这个 pane 所属的 Thread 并选中它:Thread 属于本窗口的 Space 时,就在这里打开;在打开着那个 Space 的窗口里打开,并把那个窗口带到前面;没有哪个窗口打开着那个 Space 时,则在本窗口里打开,本窗口随之切换到那个 Space。

把指针停在一张卡片上,面板旁边会再弹出一张,写着这张卡片放不下的内容:标题和完整的说明(会换行);Agent 及其状态;进度和它在等什么;然后是 会话、机器、持续(处于这个状态多久了)和 子任务。悬停卡片和按钮上的提示一样,稍停片刻才出现,刚有另一张显示过时则立刻出现,点击、按键,或指针离开窗口时消失。

提示音与通知 ​

两段内置的提示音,标出两个值得听到的时刻:一个是 Thread 完成时,另一个是它开始等你时。设置 › 工作区 › 提示音 可以关掉它们;默认是开着的。

  • 完成 在 Thread 从工作转为空闲时播放,而且只在这段工作持续了至少五秒时才响,所以一条很快的命令不会响。
  • 等待 在某个 pane 开始等待一个决定时播放,每开始等待一次响一次。同一个 Thread 里另一个 pane 开始等待,会再响一次;在问题里输入答案则不会响。

提示音跟随侧边栏的同一套状态,所以任何上报进度的程序都会让它响,而不只是 Agent。你正看着的那个 Thread 也会响:一个长任务在你面前结束,照样会播放。

在 ThinkTerm 启动前设置环境变量 THINKTERM_DISABLE_SOUND,不管设置怎么写,所有提示音都会静音,适合在演示用的机器上。Linux 上通过 paplay 或 pw-play 播放,两者都没有的系统就保持安静。连接到 ThinkTerm 的浏览器和终端界面都不会播放提示音。

设置 › 工作区 › 系统通知 默认开启,在 Thread 完成、开始等你、或因错误停下时,也会告诉系统 —— 但只在 ThinkTerm 处于后台时,而且同一个 Thread 每 30 秒最多一次。通知里写着 Thread 的名字和状态,以及其中最要紧的那个程序说的话(如果它说了)。这对屏幕上会显示什么意味着什么,见 隐私与安全。THINKTERM_DISABLE_SOUND 也会让它们静默。

点击这些通知会把 Thread 带到前面:在已经显示着它的窗口里,否则在你最后用过的窗口里,该窗口会切换到这个 Thread 的 Space。支持 macOS、Windows,以及遵循 freedesktop 通知规范的 Linux 桌面。程序自己发出的桌面通知,比如构建完成时的提醒,点击后同样会把它的 pane 带到前面。

从你自己的程序上报状态 ​

有两种方式。优先用状态上报:它是一个开放协议,其他终端也读,带着错误状态、原因、进度和子任务,而且压过所有屏幕规则。THINKTERM_AGENT 变量是 ThinkTerm 自己的,只带三种基本状态,屏幕规则可以推翻它。

状态上报(OSC 7501) ​

状态上报遵循 OSC 7501(Program Status Protocol)。Claude Code 从 2.1.295、Codex 从 0.163.0、Pi 从 1.1.0 开始发送。每当有变化,就写入一条序列,结尾用 ESC \ 或 BEL 都可以:

ESC ] 7501 ; state=<state>[:id=<path>][:kind=<kind>][:progress=<0-100>][:app=<name>][:title=<base64>][:msg=<base64>] ESC \

在 shell 里:

sh
msg=$(printf %s 'Running the test suite' | base64 | tr -d '\n')
printf '\033]7501;state=working:app=my-agent:progress=40:msg=%s\033\\' "$msg"
键含义
state必填。working、blocked、idle、done(做完了,结果还没人看过)、error(失败并停下),或者 clear:删除这条记录及其下的一切
id这条上报针对哪条记录。省略时是程序自己的记录;build/test 这样以 / 分隔的路径,会在它下面放一个子任务。最多 8 段、128 字节
kind只配合 blocked:permission、question 或 auth
progress只配合 working 或 blocked:0 到 100。省略时进度不确定
app程序的简短名字,比如 claude-code
title一个标题,UTF-8 再做 base64
msg一行说明程序在做什么或做了什么,UTF-8 再做 base64

id 和 app 里的名字只能用字母、数字和 _ . + -,每个最多 32 个字符。title 解码后最多 192 字节,msg 最多 2048 字节,两者都不能含控制字符;整条序列最多 4096 字节。违反任何一条的上报会被整条丢弃。每条上报都会整体替换它的记录,所以省略的键会被删除,而不是沿用之前的值。不认识的键会被忽略,协议就是这样扩展的。终端为每个 pane 最多保留 256 条记录,超出时丢掉最久没更新的那条。

想知道终端是否支持状态上报,先发送 ESC ] 7501 ; ? ESC \,再紧跟一个 ESC [ c。支持的终端会用同样的序列回答第一个;如果先收到的是对 ESC [ c 的回答,说明它不支持,发上报也没用。

上报不需要反复发送来保持有效。进行中的工作(working 和 blocked)在你的程序退出、或者 shell 开始下一个提示符时结束;done 和 error 会一直留到有人看过。所以用户中断或取消时报告 idle,退出前报告 done 或 error。

只剩子任务时,其中最要紧的那个代表程序:出错优先于等待,等待优先于工作,工作优先于结果。

用 THINKTERM_AGENT 上报 ​

每当状态变化时,向你的终端写入一条转义序列:

ESC ] 1337 ; SetUserVar=THINKTERM_AGENT=<base64(value)> BEL

在 shell 里:

sh
v="v1;agent=my-agent;state=working;session=$SESSION_ID;ts=$(date +%s)"
printf '\033]1337;SetUserVar=THINKTERM_AGENT=%s\007' \
  "$(printf %s "$v" | base64 | tr -d '\n')"

这条序列不渲染任何内容、不移动光标,因此可以安全地穿插在全屏输出之间。它在 SSH 和 ThinkTerm 多路复用域上都有效 —— 由持有该 pty 的终端负责解析,其值会自动跨 mux 连接镜像。

值的语法,版本 1 ​

v1;agent=<id>;state=<working|idle|blocked>[;session=<id>][;pid=<n>][;ts=<unix-seconds>][;ended=1]
字段含义
v1语法版本。v1.1 这样的次版本按版本 1 读取;其他任何版本会被完全忽略
agent必填。一个简短稳定的标识符
state必填。working、idle 或 blocked。其他值视为未知,不驱动任何指示
session你自己的会话 id,为会话恢复保留。可以为空
pid为向前兼容而接受;目前忽略
ts发出时间,unix 秒。见下文
ended=1会话已结束

未知的键会被忽略,因此这套语法可以在不破坏旧读取方的前提下扩展。之后的次版本可以加入这一版不认识的键,ThinkTerm 仍会读取它认识的那些字段。

这个值有长度上限:解码后最多 4096 字节,其中 agent 最多 128 字节,session 最多 512 字节。超出任何一项的报告,会清空该 pane 原先持有的值,而不是让上一个值继续留着。

关于 ts。 超过六小时的非 idle 上报不再具有权威性 —— 服务端上的 pane 可能比写入它的会话活得更久 —— 而超前于当前时间五分钟以上的时间戳同样处理,这样一个走快的时钟就无法让一条上报永远保鲜。在那些永远无法观测到进程的 pane 上(SSH、tmux 和串口 pane),这同一个六小时窗口还会让上报的身份过期,因为那是唯一剩下的、能提示该程序可能未道别就崩溃的信号。如果你的会话确实可能长时间保持非 idle —— 比如一个挂了一夜的审批提示 —— 请定期重发当前状态;任何一次重发都会重置该窗口。idle 上报以及不带 ts 的上报会被无限期信任。

关于 ended=1。 只要你的进程仍是该 pane 的前台组长,这个标志就会被忽略:活着的进程优先于它自己的告别。进程退出之后,该 pane 就不再被归类。

什么时候发什么。 一轮开始时发 working;准备好接受输入时发 idle;在你显示出需要作答的提示的那一刻发 blocked;该提示结束时再发相应的状态 —— 包括用户取消或中断的情况。如果你的程序能自己观测到上述所有转换,那么它完全不需要屏幕规则。

覆盖某条屏幕规则 ​

把你自己的 .toml 文件放进下面这个目录,即可替换内置清单:

~/.config/thinkterm/agent-detection/

决定替换哪个内置清单的是文件内部的 id,文件名会被忽略;不过照 设置 › Agent 的建议以 id 命名,目录会更好认。id 是新名字的文件,会新增一个 Agent,而不是替换某一个。读不了或校验不通过的文件会被整个拒绝,它本想替换的内置清单继续生效。放好之后在 Agents 面板的视图菜单里重新加载规则。

这个目录是在运行检测的那台机器上读取的,所以 ThinkTerm Connect 服务端上的 pane 遵循那台服务端主目录里的文件,而不是你的;面板上的 重新加载检测规则 也只重新加载这台电脑上的规则。

写规则时要知道一件事:标题和进度这两个区域只能看到程序实际发出过的内容。在标题或进度报告到达之前两者都是空的,并且在 pane 的程序发生变化时会被清空。展示层的兜底值 —— 由进程名推导出的标签标题、假定的"无进度" —— 永远不会进入规则。因此像 regex = ['\S'] 这样一条针对标题的规则,无法匹配上一个其程序从未发声的 pane。

相关 ​

  • Space、Project 与 Thread —— Thread 状态所在的侧边栏,以及它的通知列表
  • 窗口 —— 标签图标,pane 干活时让位给一个转圈
  • 文件与笔记 —— 右侧栏里的其他面板
  • 命令行 —— cli agent、cli events,以及从一个 pane 驱动另一个