ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

tmux 状态栏监控 AI 会话:静默检测与 WAIT 标记实践

tmux 状态栏监控 AI 会话:静默检测与 WAIT 标记实践 同时打开三四个 AI 会话是很多开发者的日常左边窗格在跑代码审查中间让本地模型生成摘要右边开着 API agent 处理接口文档。你切回终端才发现最早的任务早就输出完了正停在一个等待输入的提示符上白白浪费了几分钟。更麻烦的是你开了好多窗格每次都只能一个个敲回车看哪个“活过来”。这个问题本质上是tmux 只负责把你的多个会话保持在同一个终端里它并不关心某个会话里的 AI 是“继续输出中”还是“已经结束生成、正在等你回复”。所以这次我们来看一个实用方案用 tmux 加轻量脚本把“哪个 AI 正在等你”直接显示在状态栏上。方案不依赖具体模型不要求高显存核心就是把tmux capture-pane做成一个状态轮询器让每个 AI 窗口的“静默时长”可见。下面会先解释 tmux 为什么不够用再给出可落地的 tmux 配置、监控脚本、状态栏接入方法以及批量 AI 任务下怎么扩展。1. 多 AI 会话管理的核心能力速览先给结论这套方案能解决什么、不能解决什么看下面这张表。能力项说明目标场景同一台机器用 tmux 同时运行多个终端 AI 会话、本地模型推理或 API agent核心痛点多个 AI 会话输出完成后无法快速知道哪个会话处于等待输入状态方案类型tmux 原生能力 少量 Bash 脚本不需要新建重量级服务基础依赖tmux、bash、md5sum/md5、系统date命令显存/硬件要求不参与 AI 推理的话基本无要求参与推理则取决于具体模型启动方式先创建 tmux session 和 window再启动各 AI 客户端状态展示在 tmux status bar 实时输出每个窗口的“静默秒数”或“WAIT”标记批量任务支持按窗口命名 结束标记扩展适合多任务并行可观测粒度秒级可自行调整状态栏刷新间隔主要局限无法识别所有终端 UI 等待状态依赖静默检测或客户端输出标记这个方案适合已经习惯 tmux、同时跑多个 AI CLI 工具的开发者。如果你只是偶尔开一个对话没必要搞这套。需要手动监控多个 AI 任务并且经常切错窗口的时候这套脚本的收益才最明显。2. tmux 的定位和“不够用”的原因tmux 是一个终端复用器它的核心能力是 session、window、pane 管理。它能让你在一台远程服务器上长时间保留多个终端环境断线重连后任务还在这很重要。但它不会理解某个 pane 里的程序在做什么。tmux 默认的“活动提醒”机制比如monitor-activity是通过“窗口中是否有新输出”来判断你要不要切过去的。这对于持续打印日志的任务有效。可是 AI 会话有一个完全相反的规律它生成完内容之后恰恰是安静下来的那一刻需要你介入。比如 AI 输出完了Shell 提示符停在那里不会有新的字符滚出来。tmux 只会把它当成一个普通空闲窗口不会响铃不会亮红色也不会把窗口名改成“该你回复了”。另一个问题是窗口数量一多人眼很难跟踪。你开了 5 个 AI 窗口每个窗口都长得很像都是滚动日志、都停在某个或者$后面。仅仅靠不断切换窗口去看已经低效。状态栏最多显示几个窗口名不会告诉你哪个会话已经跑完。所以这里的判断不是“tmux 没用”而是“tmux 缺少业务状态感知”。如果你想等一批 AI 任务关键信息不在于“哪个窗口有新活动”而在于“哪个窗口超过一段时间没有活动并且现在正处于应该给输入的状态”。这个信息必须从业务侧定义然后用脚本补上去。3. 多 AI 会话等待监控的总体设计目标很容易描述在 tmux 状态栏看到类似这样的输出AI0:3s AI1:80s AI2:WAIT含义是AI0 刚刚还有内容输出可能正在生成或你刚输入过AI1 已经静默 80 秒大概率已经结束输出需要切过去看AI2 通过 wrapper 打印了[WAIT]标记明确知道它在等待你输入。实现这个效果需要四个步骤约定窗口命名规则比如 AI0、AI1、AI2每个窗口对应一个独立的 AI 会话。配置每个 AI 客户端能运行在独立 tmux window 里。写一个监控脚本定期抓取每个窗口当前的可见屏幕内容计算内容哈希。用 tmux status bar 刷新机制展示结果。如果内容哈希和上次一样说明这个窗口从上次采集到现在没有新输出于是“静默秒数”会持续增长。一旦超过阈值就认为它可能正在等待你输入。更精确的做法是如果 AI 客户端能在等待输入前输出一个固定标记比如[WAIT]脚本一旦检测到标记就直接把窗口标成 WAIT。这套方案有个好处它不修改 tmux 的工作方式只是增加一个状态轮询。所有代码都在用户目录下随时可以停掉。4. 环境准备与基础 tmux 配置建议在 Linux 服务器、macOS 或 WSL 里操作。Windows 原生环境也支持 tmux但脚本用到的命令最好统一在 WSL 或 Git Bash 下验证。先检查基础命令tmux -V bash --version如果 tmux 没有安装用系统自带包管理器安装。Ubuntu 可用aptmacOS 可用brewWSL 同样走 Linux 源。# Ubuntu/Debian sudo apt update sudo apt install -y tmux # macOS brew install tmux接下来把 tmux 状态栏刷新间隔调小一点。默认间隔可能偏长状态栏更新不够及时。建议 2 到 3 秒。在~/.tmux.conf中加入set -g base-index 1 set -g pane-base-index 1 set -g status-interval 3 set -g status-left-length 200 set -g status-left #(bash $HOME/bin/ai_status_bar.sh) set -g status-right #(whoami)#H #[fgblue]%H:%M#[default]这里的重点是status-left调用一个外部脚本。tmux 会在每隔status-interval秒时执行这个脚本并把脚本输出渲染到状态栏左侧。配置完成后重新加载tmux source-file ~/.tmux.conf如果 status-left 里脚本路径写错了状态栏会显示空内容或者报错内容。建议先用绝对路径并且先给脚本添加执行权限。5. 创建独立 AI 会话窗口我们要把多个 AI 任务分别放到不同 tmux window 里。这里定义 session 名字为aiwork初始建 3 个窗口AI0、AI1、AI2。手动创建的命令是SESSIONaiwork tmux new-session -d -s $SESSION -n AI0 tmux new-window -t $SESSION -n AI1 tmux new-window -t $SESSION -n AI2然后在对应窗口里启动 AI 客户端。不同客户端命令不一样这里用下面的方式示意# 启动 AI0 窗口中的客户端 tmux send-keys -t $SESSION:AI0 python3 my_ai_chat.py --name ai0 Enter # 启动 AI1 窗口中的客户端 tmux send-keys -t $SESSION:AI1 ollama run qwen2.5 Enter # 启动 AI2 窗口中的客户端 tmux send-keys -t $SESSION:AI2 aider --model gpt-4o Enter tmux select-window -t $SESSION:AI0 tmux attach-session -t $SESSION这些命令只是示例。你实际运行时替换成自己正在用的 AI 客户端即可。重点是窗口命名要有规则后续监控脚本才知道去遍历哪些窗口。如果希望经常反复启动可以把上面内容写成一个脚本~/bin/start_ai_session.sh#!/usr/bin/env bash set -euo pipefail SESSION${1:-aiwork} # 已存在则 attach if tmux has-session -t $SESSION 2/dev/null; then tmux attach-session -t $SESSION exit 0 fi tmux new-session -d -s $SESSION -n AI0 tmux new-window -t $SESSION -n AI1 tmux new-window -t $SESSION -n AI2 tmux send-keys -t $SESSION:AI0 bash ~/bin/ai_runner.sh ai0 Enter tmux send-keys -t $SESSION:AI1 bash ~/bin/ai_runner.sh ai1 Enter tmux send-keys -t $SESSION:AI2 bash ~/bin/ai_runner.sh ai2 Enter tmux select-window -t $SESSION:AI0 tmux attach-session -t $SESSION给脚本加执行权限chmod x ~/bin/start_ai_session.sh之后每次连接远程开发机只需执行bash ~/bin/start_ai_session.sh aiwork已经在跑就直接 attach不会重复创建窗口。6. 状态监控脚本计算每个窗口的静默时长这一步是核心。脚本要做的事简单说就是每隔几秒看一遍每个窗口的可见画面如果画面文本没有变化就认为这个窗口静止了。保存脚本到~/bin/ai_status_bar.sh内容如下#!/usr/bin/env bash # ai_status_bar.sh # 输出示例AI0:3s AI1:80s AI2:WAIT export SESSION${TMUX_SESSION:-} WAIT_THRESHOLD30 MARKER[WAIT] AI_WINDOWS(AI0 AI1 AI2) if [ -z $SESSION ]; then echo no-session exit 0 fi now$(date %s) left for win in ${AI_WINDOWS[]}; do win_name$SESSION:$win if ! tmux list-windows -F #{window_name} -t $SESSION 2/dev/null | grep -qx $win; then left${left} ${win}:off continue fi hash_file/tmp/tmux_ai_hash_${SESSION}_${win} time_file/tmp/tmux_ai_time_${SESSION}_${win} text$(tmux capture-pane -t $win_name -p 2/dev/null | md5sum | awk {print $1}) if [ ! -f $hash_file ] || [ $(cat $hash_file) ! $text ]; then echo $text $hash_file echo $now $time_file fi last_change$(cat $time_file 2/dev/null || echo $now) idle$(( now - last_change )) if echo $text | grep -q $MARKER; then left${left} #[fgyellow]${win}:WAIT#[default] elif [ $idle -gt $WAIT_THRESHOLD ]; then left${left} #[fgred]${win}:${idle}s#[default] else left${left} ${win}:${idle}s fi done echo ${left# }给脚本加上执行权限chmod x ~/bin/ai_status_bar.sh脚本设计里有几个关键点使用tmux capture-pane -p截取当前可见屏幕不读取整个滚回 buffer因此每次执行的开销很小。用md5sum计算内容指纹内容只要变化就代表该窗口有新输出。内容变化时更新时间戳内容长时间不变时idle 值会越来越大。超过WAIT_THRESHOLD秒后用红色显示提醒你切过去看。如果 AI 客户端输出了[WAIT]脚本会把该窗口标记为黄色 WAIT比单纯的静默判断更精确。需要注意如果你的系统是 macOS原生的md5sum不存在需要改成md5 -q。把脚本里的一行替换为text$(tmux capture-pane -t $win_name -p 2/dev/null | md5 -q)Linux 上继续使用md5sum即可。7. 验证效果从启动到状态栏出结果先启动一个 sessionbash ~/bin/start_ai_session.sh aiwork进入 tmux 后第一件事是确认状态栏左侧已经出现内容。正常情况会看到类似AI0:0s AI1:0s AI2:0s然后手动向 AI0 窗口发一条长文本生成任务。可以看到 AI0 窗口持续输出此时状态栏里AI0的秒数会不断重置为较小值。AI1 和 AI2 如果一直没动秒数会慢慢增长。等 AI0 输出完成停在输入提示符后它的秒数也会开始增长。要验证静默告警可以在某个窗口里故意不做任何操作等超过 30 秒。状态栏就会变成类似AI0:12s #[fgred]AI1:45s#[default] AI2:0s如果希望更明确地区分“等待输入”和“任务卡死”可以让 AI 客户端在自身进入输入循环时打印一行[WAIT]。比如写一个简单的 wrapper#!/usr/bin/env bash # ai_runner.sh echo [WAIT] start python3 my_ai_chat.py echo [WAIT] end实际项目中my_ai_chat.py内部会在每次收到模型完整输出后打印一个[WAIT]再进入下一次输入。这样监控脚本就能直接识别。输入验证样例# 向 AI2 发送内容 tmux send-keys -t aiwork:AI2 帮我解释一下这段代码 Enter等 AI2 输出完毕后如果 wrapper 在界面里打印了[WAIT]状态栏该窗口会立刻变为黄色AI2:WAIT。这套流程跑通后你不需要再逐个切换窗口看有没有卡住。判断依据已经从“人眼扫描”变成了“状态栏红黄提示”。8. 让等待检测结果更可靠利用输出标记纯静默检测有一个问题某些 AI 终端工具在等待输入时会一直显示转圈动画、光标闪烁或刷新进度条。每次状态栏刷新时内容都不同静默秒数会被不断重置于是脚本一直认为“这个窗口还在工作”。解决方式就是前面提到的[WAIT]标记。它的思路是不要在状态层面猜测而是让 AI 客户端自己告诉你“我现在要向你提问了”。在 wrapper 里增加标记时建议输出一个不容易与业务内容混淆的字符串。例如# my_ai_chat.py 示例片段 while True: user_prompt input( ) if user_prompt.strip(): continue # 关键进入输入等待前打印标记 print([WAIT], flushTrue)这只是一个极简示范。实际终端客户端不一定允许你随意插入这种输出常见做法有几种如果 AI 客户端本身提供 hook比如每次生成结束后会执行某个 shell 命令那是最理想的接入点。如果客户端不是交互式终端而是从 stdin 读取输入输出到 stdoutwrapper 可以在一次完整交互结束时补打标记。如果客户端支持自定义 prompt 格式可以把[WAIT]写进提示符然后监控脚本去匹配该前缀。如果客户端完全无法改那就退回到静默检测方案并把阈值设得更保守一些。不管用哪种方式核心是让监控脚本识别的是业务状态而不是单纯看终端有没有动静。这样可以有效减少误报和漏报。9. 接入批量任务同一时间跑多个 AI 队列除了在线聊天式 AI批量任务场景也适配这套监控。比如你有job-a、job-b、job-c三个目录分别要交给同一个大模型 API 去跑。你可以把每个 window 命名成任务名然后用同样的状态栏监控。举一个批量任务脚本结构#!/usr/bin/env bash # run_batch_job.sh # 用法bash run_batch_job.sh job-a job$1 echo [START] $job python3 batch_worker.py --job $job /tmp/logs/${job}.log 21 code$? echo [EXIT] $code /tmp/logs/${job}.log echo [WAIT] job finished启动任务时让每个任务跑在一个独立窗口SESSIONbatch tmux new-session -d -s $SESSION -n job-a tmux send-keys -t $SESSION:job-a bash ~/bin/run_batch_job.sh job-a Enter批量任务通常不需要人工干预所以这里的[WAIT]更准确的语义是“这个任务已经跑完可以看结果了”。你可以把MARKER改成[DONE]再让监控脚本区分颜色绿色表示正在运行秒数小黄色表示命中[DONE]可以去读日志红色表示静默超过阈值可能卡死或崩溃。如果任务数量很大把AI_WINDOWS(job-a job-b job-c)改为动态发现即可。在 Bash 脚本里可以用tmux list-windows动态组装窗口列表。这里提供第三种代码变体#!/usr/bin/env bash # 动态发现 session 内所有窗口 mapfile -t AI_WINDOWS (tmux list-windows -F #{window_name} -t $SESSION 2/dev/null)这样新增任务窗口时不需要修改监控脚本和 tmux 配置。批量任务建议每个任务都在独立窗口运行同时把完整日志写到磁盘文件。日志是批次审计的依据不能只依赖终端回显。10. 资源占用与状态栏刷新性能这套方案的资源占用主要集中在两个操作上tmux capture-pane抓取屏幕内容和计算文本哈希。每次抓取当前可见屏幕并不抓整个回滚历史所以耗时很短。状态栏默认 3 秒刷新一次每个窗口执行一次capture-pane和一次md5sum。三个窗口的情况下平均 CPU 占用可以忽略。如果你有 10 个窗口也可以把status-interval调大到 5 秒或 8 秒减少刷新频率。需要注意的变量是窗口尺寸越大capture-pane返回的文本量越大哈希计算耗时越长。窗口数量很多时建议不要开超大终端窗口。如果 AI 客户端处于持续刷新状态比如转圈动画、实时 token 计数那么每次抓去的文本都会变化脚本就会频繁更新时间戳idle 一直很小。状态栏刷新越频繁视觉上越灵敏但 CPU 占用也会增加。对普通开发机来说2 到 3 秒是比较舒适的区间。使用md5sum计算不会对 tmux 主服务造成明显压力因为每次调用的只是外部进程。临时文件方面脚本会为每个窗口生成两个文件存放在/tmp下。窗口关闭后这些文件不会自动清理。如果想清理rm -f /tmp/tmux_ai_hash_* /tmp/tmux_ai_time_*如果要长期运行建议把这些 hash 文件放到一个固定目录方便监控和清理。例如mkdir -p ~/.cache/tmux_ai_status hash_file$HOME/.cache/tmux_ai_status/${SESSION}_${win}.hash time_file$HOME/.cache/tmux_ai_status/${SESSION}_${win}.time11. 常见问题与排查方法下面这张表列出了实际使用中最容易出现的问题和排查思路。问题现象可能原因排查方式解决方案状态栏一直显示 no sessionTMUX_SESSION变量为空脚本没有拿到当前 session 名在 tmux 内执行echo $TMUX_SESSION脚本里 fallbackexport SESSION${TMUX_SESSION:-$(tmux display-message -p #{session_name})}状态栏空白status-left 脚本路径错误或文件不可执行检查~/.tmux.conf中的路径和权限使用绝对路径执行chmod x窗口长时间不更新状态显示 0s窗口内有动画或光标闪烁capute 内容一直变化打开该窗口观察输出节奏改用手动[WAIT]标记方式判断明明已经输出完成但状态栏不红WAIT_THRESHOLD设置太大还没到阈值查看当前 idle 值把阈值降到 15 到 30 秒之间脚本频繁执行导致 CPU 较高status-interval太短或窗口数太多用top查看 bash 进程把status-interval调大到 5 秒以上某些窗口不存在状态栏有 off 标记创建 session 时没创建对应窗口检查 session 窗口列表确认命名后再启动监控脚本macOS 上md5sum找不到macOS 自带md5执行which md5脚本中将md5sum | awk替换为md5 -qtmux source-file 后状态栏没变化配置有语法错误或脚本输出包含换行终端执行tmux source-file ~/.tmux.conf看报错修正配置或脚本中的echo输出状态栏显示颜色代码而不是颜色status-left 用了#[fgred]但 tmux 未开启颜色终端检查TERM设置使用screen-256color或tmux-256color用[WAIT]标记失败wrapper 输出内容被滚动屏幕冲掉或 AI 客户端吞掉了标记用日志文件查看 wrapper 输出增加tee /tmp/ai.log记录完整输出最常踩的坑是状态栏脚本执行时没有 session 上下文。在 tmux 外部直接测试ai_status_bar.sh时TMUX_SESSION为空脚本自然不知道遍历哪个 session。建议在脚本里加入 session 自动发现if [ -z $SESSION ]; then if tmux display-message -p #{session_name} /dev/null 21; then SESSION$(tmux display-message -p #{session_name}) else echo no-session exit 0 fi fi12. 最佳实践与使用建议这套方法的价值不在于“监控脚本本身多聪明”而在于给你的多 AI 工作流增加了一个统一状态层。以下几点可以让它在实际项目里更稳定。第一给每个 AI 客户端套一层统一的 runner wrapper。wrapper 负责做三件事设置窗口名、记录日志、在关键节点输出[WAIT]或[DONE]。这样所有 AI 任务的输出格式就统一了监控脚本只需要接收这个约定。不要嫌这层封装多余等你要跑 5 个并发 AI 任务的时候它给你的不只是状态栏提示还有完整日志和退出码。第二窗口命名尽量固定成AI0、AI1、AI2或者直接使用任务名。不要用随机生成的窗口名否则动态监控脚本很难维护。第三对于涉及代码仓库、文档、私有数据的 AI 会话必须先确认数据使用的授权边界。不要因为任务跑得太顺手就把内部敏感代码随意发给没有授权的第三方 API。本地模型可以自行控制外部 API 要看服务条款和权限。第四不要把WAIT_THRESHOLD设得太小。AI 思维链比较长的任务中间可能几十秒没有输出容易误报。第一次运行时先用 60 秒观察再根据自己的习惯改成 30 秒或 20 秒。第五状态栏显示红色后第一件事不是马上发消息而是切到对应窗口看清楚是“等待输入”还是“任务卡死”。在 wrapper 设计阶段尽量给“完成”和“异常”分别打标记。没有标记时至少通过日志最后几行判断。第六如果 tmux 本身的状态栏不够用可以考虑再开一个 dashboard window用tmux select-layout把监控脚本放到一个独立 pane 里持续输出。这适合窗口特别多的情况。13. 总结与下一步回到开头的场景以前你要频繁切换窗口在多个 AI 会话里找哪个停了。现在只需要让 tmux 状态栏显示AI1:80s和AI2:WAIT你就能决定下一步切到哪个窗口。建议先做最小验证按文章里的示例脚本建 3 个 AI 窗口跑通状态栏显示再慢慢加入[WAIT]标记和批量任务。最先要验证的是静默检测方案这是不依赖具体客户端的通用底座。最容易踩的坑是 AI 客户端有动画刷新导致静默检测失效这种情况就用 wrapper 标记兜底。后续如果你想继续扩展可以考虑把状态栏输出转发到通知系统。比如心跳检测到某个窗口超过 5 分钟没有变化时用系统通知提醒你。也可以把监控脚本从 Bash 换成 Python加上更准确的终端交互状态判断。这套方案不复杂但它能让“多开 AI”真正变得可控。先跑
返回列表