Agent Monitor:基于 tmux 的移动端终端监控方案
Agent Monitor 是一个运行在开发机上的轻量 Web 工具,用来在手机上查看和接管本机正在运行的终端任务,重点场景是 Claude Code、Codex、测试命令和构建命令。
核心思路不是做远程桌面,也不是改造 Ghostty,而是把 tmux 当成终端会话的状态源。所有 AI agent 都跑在 tmux pane 里,服务端定期读取 pane 列表和最近输出,再通过手机端页面展示状态;需要干预时,再把输入通过 tmux send-keys 发回对应 pane。
这更像给现有终端工作流外挂一个 observer service,而不是侵入 Ghostty runtime。
目标
这个工具解决的是“人离开电脑,但 AI agent 还在工作”的状态可见性问题。
需要支持:
- 在手机上看到当前有哪些 tmux session / pane。
- 看到每个 pane 最近输出,判断 Codex / Claude 是否还在跑。
- 识别常见状态:运行中、等待输入、失败、完成、空闲。
- 在手机上回复一段文本并发送 Enter。
- 提供少量快捷键,例如 Enter、Ctrl-C、Ctrl-D、Esc。
- 对 stale session 提供 kill session 能力,避免已无价值的会话长期挂着。
- 通过 token 做最小鉴权,再结合 Tailscale、局域网或 Cloudflare Tunnel 访问。
非目标:
- 不做完整远程桌面。
- 不做多用户账号系统。
- 不保存长期历史。
- 不直接依赖 Ghostty 内部 API。
产品形态
电脑端启动一个本地服务:
AGENT_MONITOR_HOST=0.0.0.0 \
AGENT_MONITOR_PORT=8797 \
AGENT_MONITOR_TOKEN=<token> \
bun run start手机打开:
http://<开发机 IP>:8797/?token=<token>首页是一个 dashboard:
Waiting 1
Running 3
Failed 0
Done 2
Idle 4下面是 pane 列表,每一项展示:
- session / window 名称
- 当前命令,例如
codex、claude、node、zsh - 推断出的状态和原因
- 最近几行输出
点进详情页后展示:
- tmux target,例如
work:0.1 - cwd
- pid
- 最近 300 行输出
- 输入框
- 快捷键按钮
Kill tmux session危险操作按钮
架构
当前实现位于:
/Users/not/projects/devs/opensource/agent-monitor目录结构:
agent-monitor
├── src/server.ts
├── public/index.html
├── public/app.js
├── public/styles.css
├── package.json
└── README.md整体链路:
手机浏览器
|
| HTTP / WebSocket
v
Bun server
|
| tmux list-panes / capture-pane / send-keys / kill-session
v
tmux sessions
|
| panes
v
Claude Code / Codex / tests / shell这里的 Bun server 类似一个 Hono/NestJS handler 层,只是数据源不是 PostgreSQL 或 ClickHouse,而是 tmux 的命令行接口。
服务端职责
src/server.ts 做几件事:
发现 pane
通过:
tmux list-panes -a -F '<format>'读取所有 session、window、pane 信息。
当前采集字段包括:
session_namewindow_indexwindow_namepane_indexpane_idpane_current_commandpane_current_pathpane_activepane_pidpane_title
服务端把它们整理成统一 Pane 对象。
抓取最近输出
通过:
tmux capture-pane -p -J -S -300 -t <pane_id>读取最近 300 行。-J 会 join wrapped lines,让手机上看到的文本更接近实际语义,而不是纯粹按终端宽度折行。
状态推断
当前是轻量规则,不引入模型:
- 最近输出包含
Do you want、Proceed?、Continue?、y/n等,标记为waiting。 - 最近输出包含
failed、error:、panic:、traceback等,标记为failed。 - 最近输出包含
success、completed、done、tests passed等,标记为done。 - 当前命令是
claude、codex、node、bun、npm、zig等,标记为running。 - 当前命令是
zsh、bash、fish、nu且没有明显输出,标记为idle。
这套规则不追求完美,只负责给手机端提供足够可用的 first signal。
发送输入
文本回复使用:
tmux send-keys -t <pane_id> -l '<text>'
tmux send-keys -t <pane_id> Enter快捷键使用:
tmux send-keys -t <pane_id> C-c
tmux send-keys -t <pane_id> C-d
tmux send-keys -t <pane_id> C-[Esc 按钮实际发送 C-[,比直接发送 Escape 更兼容 Claude Code / Codex 这类 TUI。
删除 stale session
详情页提供 Kill tmux session,后端调用:
tmux kill-session -t <session>前端必须先弹确认框,避免误删正在跑的任务。
API
GET /api/snapshot
返回当前所有 pane 的快照。
鉴权:
?token=<token>
Authorization: Bearer <token>响应结构:
{
"ok": true,
"now": "2026-05-03T00:00:00.000Z",
"panes": [
{
"id": "%4",
"target": "work:0.1",
"session": "work",
"command": "codex",
"path": "/Users/not/projects/devs/opensource/ghostty",
"tail": "...",
"status": "waiting",
"reason": "looks like it needs input"
}
]
}POST /api/send
向 pane 输入文本。
{
"paneId": "%4",
"text": "继续",
"enter": true
}POST /api/key
发送快捷键。
/api/key?paneId=%254&key=C-c允许的 key:
EnterC-cC-dC-[EscapeUpDown
POST /api/session/kill
删除整个 tmux session。
{
"session": "cx_ghostty_03a5805c"
}cc / cx alias 改造
为了让 Codex 和 Claude Code 默认跑进 tmux,~/.zshrc 里的 cc / cx 从 alias 改成了 function。
目标行为:
- 在当前目录执行
cc,进入或创建cc_<目录名>_<路径hash>session。 - 在当前目录执行
cx,进入或创建cx_<目录名>_<路径hash>session。 - 如果 session 已存在,直接进入。
- 如果 session 不存在,先创建一个正常 shell,再用
tmux send-keys发送启动命令。 - 在 tmux 外执行时使用
attach-session。 - 在 tmux 内执行时使用
switch-client。
示例:
cd /Users/not/projects/devs/opensource/ghostty
cx会创建或进入类似:
cx_ghostty_03a5805csession 内启动:
codex --yolocc 对应:
claude --dangerously-skip-permissions一个关键修正是命令拼接不能把整个命令整体转义成 codex\ --yolo,否则 zsh 会尝试寻找名字叫 codex --yolo 的命令。正确做法是逐参数 quote 后再 join。
为什么选择 tmux,而不是直接改 Ghostty
Ghostty 源码里确实有可以利用的事件:
start_commandstop_commandprogress_reportdesktop_notificationpwd_changering_bell
也能从终端模型里拿到当前 viewport 文本。理论上可以做 Ghostty-native remote status server。
但第一版选 tmux 更合适:
- 不侵入 Ghostty,不需要维护 fork。
- 对任何终端都有效,Ghostty、iTerm2、原生 Terminal 都能用。
- 对 Claude Code / Codex 的真实运行环境更直接,因为 tmux 就是它们所在的 PTY。
- 更容易通过手机发送输入和快捷键。
- 更容易清理 stale session。
可以类比为:先在服务外层加一个观测和控制 sidecar,而不是直接改主框架 runtime。
访问与安全
推荐访问方式:
- 本机调试:
127.0.0.1 - 同 Wi-Fi:开发机局域网 IP
- 跨网络:Tailscale
- 必要时:Cloudflare Tunnel
服务默认使用 token:
AGENT_MONITOR_TOKEN="$(openssl rand -hex 18)"手机 URL:
http://<ip>:8797/?token=<token>注意:
- 不建议无 token 暴露到公网。
send-keys和kill-session都是高权限操作,等价于远程控制你的 tmux。- 如果使用 Cloudflare Tunnel,应该再叠加 Access 或至少使用长随机 token。
当前限制
- 状态识别是正则规则,不是严格状态机。
- 没有持久化历史,刷新后只能看当前 tmux scrollback。
- 没有多机器聚合。
- 没有 push notification。
- 没有 session 白名单,任何当前用户可见的 tmux session 都会展示。
kill-session是整 session 删除,不是只关单个 pane。
后续方向
优先级较高的增强:
- session/pane tag:给 pane 标记
ghostty-codex、frontend-claude等名称。 - waiting detector 增强:识别更多 Codex / Claude Code 的确认提示。
- push 通知:等待输入、失败、完成时推到手机。
- pane 级 kill:支持只关闭一个 pane,而不是整个 session。
- 只读模式:手机端只观察,不允许 send-keys。
- 多机器:多个开发机上报到一个统一 dashboard。
更长期可以考虑 Ghostty-native 集成,把 Ghostty 的 command lifecycle 和 viewport dump 作为更结构化的数据源。但在真实需求被验证前,tmux 方案已经足够覆盖主要工作流。