#!/usr/bin/env bash # claude-brain 巡检:列出本会话每个子 agent 的最近活动时间、空闲时长与最后动作, # 标出空闲超过阈值的 worker,供调度员每 30 分钟打印并决定是否唤醒。 # # 原理:Claude Code 把每个 subagent 的转录写在 # ~/.claude/projects/<项目slug>/<会话ID>/subagents/agent-.jsonl # 旁边的 agent-.meta.json 带 description(派出时的 description 参数)与 model。 # 转录文件的 mtime 就是该 agent 最近一次活动;最后几 KB 能看出它停在什么动作上。 # # 用法(在 claude-brain 运行时所在的工程根目录执行): # check_agents.sh [运行目录] [--threshold 30] [--hours 24] [--session <会话ID>] [--ids id1,id2] # 运行目录给出时,末尾顺带打印其中的 agents.md 名册。 # --ids 只看这些 agent(前缀即可,逗号分隔);不给则列出时间窗内所有有转录的 agent。 # 输出:先一行 "巡检 HH:MM",然后 markdown 表。判定列: # 正常 / 空闲超限 / 正常(大脑等待不计) / 已结束?(回合结束且空闲超限,需对照名册) threshold=30 hours=24 session="" ids="" rundir="" while [ $# -gt 0 ]; do case "$1" in --threshold) threshold="$2"; shift 2 ;; --hours) hours="$2"; shift 2 ;; --session) session="$2"; shift 2 ;; --ids) ids="$2"; shift 2 ;; *) rundir="$1"; shift ;; esac done cwd="$(pwd -W 2>/dev/null || pwd)" slug="$(printf '%s' "$cwd" | sed -e 's#[:/\\_]#-#g')" proj="$HOME/.claude/projects/$slug" if [ ! -d "$proj" ]; then echo "找不到项目转录目录: $proj" >&2 echo "请在 claude-brain 运行时所在的工程根目录执行本脚本。" >&2 exit 1 fi mins=$((hours * 60)) if [ -n "$session" ]; then files=$(find "$proj/$session/subagents" -name 'agent-*.jsonl' 2>/dev/null) else files=$(find "$proj" -path '*/subagents/agent-*.jsonl' -mmin "-$mins" 2>/dev/null) fi now=$(date +%s) echo "巡检 $(date +%H:%M)(阈值 ${threshold} min,时间窗 ${hours} h)" echo echo "| agent | 角色/任务 | 模型 | 最近活动 | 空闲 | 最后动作 | 判定 |" echo "|---|---|---|---|---|---|---|" over=0 # 按 mtime 新→旧排序 for f in $(ls -t $files 2>/dev/null); do id=$(basename "$f" .jsonl | sed 's/^agent-//') if [ -n "$ids" ]; then hit=0 for p in $(printf '%s' "$ids" | tr ',' ' '); do case "$id" in "$p"*) hit=1 ;; esac done [ $hit -eq 1 ] || continue fi meta="${f%.jsonl}.meta.json" desc="?"; model="?" if [ -f "$meta" ]; then desc=$(grep -o '"description":"[^"]*"' "$meta" | head -n1 | sed 's/"description":"//;s/"$//') model=$(grep -o '"model":"[^"]*"' "$meta" | head -n1 | sed 's/"model":"//;s/"$//') fi mt=$(stat -c %Y "$f") idle=$(( (now - mt) / 60 )) last=$(date -d "@$mt" +%H:%M) tailtxt=$(tail -c 6000 "$f") if printf '%s' "$tailtxt" | grep -q '"name":"Monitor"'; then act="等待 Monitor" elif printf '%s' "$tailtxt" | grep -q '"run_in_background":true'; then act="等待后台命令" elif printf '%s' "$tailtxt" | grep -q 'Monitor expired\|Monitor event'; then act="收到 Monitor 事件后停住" elif printf '%s' "$tailtxt" | tail -c 1500 | grep -q '"stop_reason":"end_turn"'; then act="回合结束" elif printf '%s' "$tailtxt" | grep -q '"type":"tool_use"'; then act="工具调用中" else act="活动中" fi isbrain=0 case "$desc" in *大脑*|*brain*|*Brain*) isbrain=1 ;; esac if [ $isbrain -eq 1 ]; then verdict="正常(大脑等待不计)" elif [ "$idle" -ge "$threshold" ]; then if [ "$act" = "回合结束" ]; then verdict="已结束?(对照名册;运行中则为空闲超限)"; else verdict="**空闲超限**"; fi over=$((over + 1)) else verdict="正常" fi printf '| %s… | %s | %s | %s | %s min | %s | %s |\n' "${id:0:5}" "$desc" "$model" "$last" "$idle" "$act" "$verdict" done echo if [ "$over" -gt 0 ]; then echo "有 $over 个 agent 空闲 ≥ ${threshold} min。对照 agents.md:状态为「运行中」且报告文件不存在的,按 SKILL.md「30 分钟巡检」第 4 步介入。" else echo "无空闲超限的 agent。" fi if [ -n "$rundir" ] && [ -f "$rundir/agents.md" ]; then echo cat "$rundir/agents.md" fi