サブエージェントが「動いてる/終わった」を当てる
"🤖 evaluator" と表示して、終わったら消す。それだけのことを、正確にやるための小さな工夫。
ccmux のペインのタイトルには、こんな感じでサブエージェントが出ます:
● claude [1] 🤖 evaluator, reviewer 🔧 Bash- 今
evaluatorとreviewerの 2 つのサブエージェントが走ってる - そのうち片方が Bash を実行中
どちらかが終わると、その名前が消えます。両方終われば 🤖 自体が消える。
どうやってこの “動いている感” を追跡してるか の話です。
見えているもの
前回 で書いた通り、ccmux は Claude Code のログファイル(JSONL)を読んでいます。その中に、こんなペアが現れる:
起動時:
{ "type": "assistant", "message": { "content": [{ "type": "tool_use", "id": "toolu_01ABC", "name": "Agent", "input": { "subagent_type": "evaluator" } }] }}完了時:
{ "type": "user", "message": { "content": [{ "type": "tool_result", "tool_use_id": "toolu_01ABC", "content": "..." }] }}ポイントは id と tool_use_id が同じ値でペアになっている こと。この 2 つが見つかれば、開始と終了を紐づけられます。
HashMap で「今動いてるやつ」を持つ
データ構造は、ほとんど集合演算です:
pub struct PaneMonitor { // tool_use_id → サブエージェント種別 active_task_ids: HashMap<String, String>, // ...}起動を見たら入れる。完了を見たら消す。それだけ。
// tool_use が来た時if name == "Agent" { if let Some(kind) = input.get("subagent_type").and_then(|v| v.as_str()) { active_task_ids.insert(id.to_string(), kind.to_string()); }}
// tool_result が来た時active_task_ids.remove(tool_use_id);表示用のリストは、今持っている HashMap の値を並べるだけ:
let mut types: Vec<String> = active_task_ids.values().cloned().collect();types.sort();types.dedup();これで「同じ種類のサブエージェントが 2 つ動いていても 1 つに見える」まで含めて実装終わり。
ハマりどころ 1: 名前が違う
最初は「サブエージェントのツール名は Task だろう」と決めつけてました。でも実ファイルを見たら Agent でした。Task だけマッチさせてた初期実装は、何も表示されないという静かな失敗。
ドキュメントの記述と、実際に流れてるバイトが食い違うことはある。一次情報が一番強い。
ハマりどころ 2: 間に他のイベントが混ざる
起動イベントと完了イベントは、連続して現れるとは限りません。間には:
- ユーザーのメッセージ
- 他の tool_use
- アシスタントのテキスト応答
などが入り乱れます。だから「次に来るのが対応する完了」とは仮定しない。必ず tool_use_id で紐づける。
ハマりどころ 3: 親がクラッシュしたらどうなる
Claude Code のセッションを強制終了すると、完了イベントだけ書かれずに終わるケースがあります。すると ccmux 側の HashMap に「永遠に走ってることになっているサブエージェント」が残ってしまう。
対策は愚直で、別セッションのログファイルが現れたら、全部リセット:
if new_path != self.jsonl_path { self.active_task_ids.clear(); self.state = ClaudeState::default(); self.jsonl_path = new_path;}ccmux 自体を再起動してもリセットされます。状態は全部 JSONL 由来なので、起動時に再スキャンするだけで復元されます。
全体像
結局やっていることは:
- 起動 → HashMap に入れる
- 完了 → HashMap から消す
- 表示 → HashMap の中身を並べる
「JSON の流れから状態を持つ」のは、こういう時に書き味がシンプルになります。イベントストリームに対して HashMap が 1 つある、という形は覚えておくと他でも使えます。
次回は Claude Code を「起動してますよ」と判定する別のトリック(OSC ウィンドウタイトル)について書きます。