#!/bin/bash # ============================================================================ # team-pane-cleanup.sh — Agent Team tmux Pane 清理 Hook # # 用于 PostToolUse(TeamDelete) 事件 # 读取 .claude/team-panes.json,关闭所有记录的 agent pane # 跳过 PM pane,仅关闭 agent pane # # 清理策略: # 1. Ctrl+C 终止前台进程 # 2. 等待 3 秒让 Claude Code 优雅退出 # 3. 无条件 kill-pane 强制关闭 # 4. 验证 pane 确实已关闭 # ============================================================================ set -euo pipefail # 项目根目录 PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" STATE_FILE="${PROJECT_ROOT}/.claude/team-panes.json" LOG_FILE="${PROJECT_ROOT}/.claude/team-panes.log" # 日志函数 log() { local msg="[$(date '+%Y-%m-%d %H:%M:%S')] team-pane-cleanup: $*" echo "${msg}" >&2 echo "${msg}" >> "${LOG_FILE}" 2>/dev/null || true } # 读取 stdin(必须读取,但不使用) timeout 3 cat >/dev/null 2>&1 || true # 状态文件不存在 → 无需清理 if [ ! -f "${STATE_FILE}" ]; then log "状态文件不存在,无需清理" exit 0 fi log "开始清理流程" # 读取 agent_pane_ids AGENT_PANES=$(jq -r '.agent_pane_ids[]?' "${STATE_FILE}" 2>/dev/null || true) PM_PANE=$(jq -r '.pm_pane_id // empty' "${STATE_FILE}" 2>/dev/null || true) TEAM_NAME=$(jq -r '.team_name // empty' "${STATE_FILE}" 2>/dev/null || true) log "team=${TEAM_NAME}, PM=${PM_PANE}, agent_panes=[${AGENT_PANES//$'\n'/, }]" if [ -z "${AGENT_PANES}" ]; then log "无 agent pane 需要清理" rm -f "${STATE_FILE}" rm -f /tmp/claude-pane-snapshot-* 2>/dev/null || true exit 0 fi # 关闭单个 pane 的函数 # 参数: $1 = pane_id # 返回: 0=成功关闭, 1=关闭失败 close_pane() { local pane_id="$1" # 安全检查:确保不是 PM pane if [ "${pane_id}" == "${PM_PANE}" ]; then log "跳过 PM pane ${pane_id}" return 1 fi # 检查 pane 是否仍存在 if ! tmux list-panes -t "${pane_id}" 2>/dev/null | head -1 | grep -q .; then log "pane ${pane_id} 已不存在" return 0 fi log "关闭 pane ${pane_id} ..." # 第 1 步: 发送 Ctrl+C 终止运行中的进程 tmux send-keys -t "${pane_id}" C-c 2>/dev/null || true sleep 1 # 第 2 步: 再次发送 Ctrl+C(Claude Code 可能需要两次中断才退出) tmux send-keys -t "${pane_id}" C-c 2>/dev/null || true sleep 2 # 第 3 步: 如果进程已退出到 bash,发送 exit tmux send-keys -t "${pane_id}" 'exit' Enter 2>/dev/null || true sleep 1 # 第 4 步: 无条件 kill-pane(不管 pane 是否仍在) tmux kill-pane -t "${pane_id}" 2>/dev/null || true # 第 5 步: 验证 pane 确实已关闭 sleep 0.5 if tmux list-panes -t "${pane_id}" 2>/dev/null | head -1 | grep -q .; then log "警告: pane ${pane_id} 仍在,尝试二次 kill-pane" tmux kill-pane -t "${pane_id}" 2>/dev/null || true sleep 0.5 if tmux list-panes -t "${pane_id}" 2>/dev/null | head -1 | grep -q .; then log "错误: pane ${pane_id} 无法关闭" return 1 fi fi log "pane ${pane_id} 已成功关闭" return 0 } # 关闭每个 agent pane CLOSED_COUNT=0 FAILED_COUNT=0 while IFS= read -r pane_id; do [ -z "${pane_id}" ] && continue if close_pane "${pane_id}"; then CLOSED_COUNT=$((CLOSED_COUNT + 1)) else FAILED_COUNT=$((FAILED_COUNT + 1)) fi done <<< "${AGENT_PANES}" log "清理完成: 成功=${CLOSED_COUNT}, 失败=${FAILED_COUNT}" # 清理状态文件 rm -f "${STATE_FILE}" # 清理所有关联的快照文件 rm -f /tmp/claude-pane-snapshot-* 2>/dev/null || true exit 0