Agent 检测
MadoHub 如何识别正在运行的 AI Agent 并追踪其状态。
MadoHub 会实时监控终端输出,识别当前运行的是哪个 AI Agent,以及它处于什么状态。这个能力支撑了图块标题栏上的可视化指示器,以及路由连接的触发逻辑。
支持的 Agent
Claude Code
通过终端输出中的 Unicode 标记进行检测:
| 标记 | 含义 |
|---|---|
| ❯ (U+2770) | Claude Code 提示符 |
| ✽ (U+273D) | 思考/加载指示器 |
| ⏺ (U+23FA) | 工具使用指示器 |
同时也会匹配输出中的字面字符串 "Claude Code"。
OpenAI Codex CLI
通过以下模式进行检测:
OpenAI Codex— 品牌行codex>— TUI 提示符openai/codex— 包引用codex v[0-9]— 版本字符串> _ OpenAI Codex— 启动横幅codex --dangerously— 沙箱绕过标志(不区分大小写)codex...fast— 快速模型调用(不区分大小写)gpt-[0-9.]+ x?high— 模型信息
Amp 和 OpenCode
Amp 和 OpenCode 同样被识别为 Agent 类型,但不是通过上述前端终端文本探测器识别的——该探测器只能分类 Claude Code、Codex 和通用 shell。Amp 和 OpenCode 会话是单独识别的:由后端的进程树扫描来完成,它会遍历终端的子进程,查找它们对应的可执行文件。
通用 shell
如果没有检测到任何 Agent 标记,终端会被当作标准 shell 会话处理。
Agent 状态
MadoHub 为每个终端追踪七种状态:
| 状态 | 检测方式 | 可视效果 |
|---|---|---|
| Working | 状态栏中出现 "esc to interrupt",或出现带省略号的 ✽/⏺ | 绿色脉冲边框 |
| Idle | 状态栏中出现 "? for shortcuts" | 无指示器 |
| Waiting | 出现 "Allow once" / "Allow always" / "Deny" / "Do you want to proceed?" / "(y/n)" / "requires approval" 对话框 | 橙色边框 |
| Stopped | 通过终端抓取检测到完成关键词(存在歧义——任务可能成功结束,也可能没有) | 红色指示器 |
| Completed | 确认成功结束,从 Agent 自身的 JSONL/DB 会话日志中解析得出(例如 Codex 的 task_complete) | 绿色指示器 |
| Failed | 确认失败,从 Agent 自身的 JSONL/DB 会话日志中解析得出(例如 Codex 的 turn_aborted / error) | 红色指示器 |
| Exited | 底层 PTY 进程已消失(终态) | 灰色/暗淡指示器 |
每个状态都带有 stateSource 标记,取值为 pty-scrape 或 jsonl-watcher,让 UI(以及任何路由逻辑)能够区分"仅凭终端实时抓取推测出的结果"和"通过读取 Agent 自身会话记录确认的结论"。"Stopped" 始终是 pty-scrape,本质上具有歧义;"Completed" 和 "Failed" 来自 jsonl-watcher,被视为权威结果。
完成检测
MadoHub 判断 Agent 是否完成任务,靠的是匹配一种形状(shape),而不是固定的关键词列表:行首的加载符号,紧跟一个大写开头的过去式动词,再紧跟 " for " 和一段时长。
正则表达式模式:
/(?:^\s*[✶✽✳✢·∗*]\s*([A-Z][a-zé]+(?:ed|t))|\b(Saut[ée]*d|Cogitated))\s+for\s+(?:\d+\s*[hm]\s*)*\d+\s*[hms]\b/Claude Code 在不同版本之间会轮换使用不同的动词(Worked、Churned、Sautéed、Cogitated 等),所以动词本身是故意不做穷举的——匹配依赖的是形状本身。一个真实的匹配示例:
✽ Worked for 2m 30s行首的符号锚点(✶✽✳✢·∗* 之一)正是用来避免把普通文本(例如 "Retried for 3s")误判为完成的关键所在。Sautéed 和 Cogitated 之所以还留在该模式中,仅仅是作为没有符号锚点的旧版兜底方案,用来兼容早于符号锚定格式的老版本 CLI 输出。
当检测到完成状态时,Agent 状态会变为 "stopped",并且任何已连接的路由触发器都可能随之触发。