128 lines
3.7 KiB
Bash
Executable File
128 lines
3.7 KiB
Bash
Executable File
#!/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
|