Files
2026-04-19 23:12:21 +08:00

338 lines
13 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# tmux-team.sh — 管理 tmux 4 Pane 团队工作空间
#
# 使用方法:
# bash scripts/tmux-team.sh # 创建默认会话 isos-team
# bash scripts/tmux-team.sh <名称> # 创建指定名称的会话
# bash scripts/tmux-team.sh -k [名称] # 杀死会话 (默认 isos-team)
# bash scripts/tmux-team.sh -l # 列出所有会话
# bash scripts/tmux-team.sh -a [名称] # 附加到已有会话
# bash scripts/tmux-team.sh -h # 显示帮助
#
# 行为 (create):
# - 已在 tmux 中: 直接在当前窗口构建布局
# - 不在 tmux 中: 启动新 tmux 会话并附加
#
# 视觉布局:
# +----------+--------------------+--------------------+
# | | Pane 2 (后端) | Pane 3 (前端) |
# | Pane 1 +--------------------+--------------------+
# | (项目) | Pane 4 (测试) | Pane 5 (对话) |
# | | | |
# +----------+--------------------+--------------------+
set -euo pipefail
WORKSPACE="/workspace"
DEFAULT_SESSION="isos-team"
# ── 参数解析 ──────────────────────────────────────────────
ACTION="create"
SESSION_NAME=""
while [ $# -gt 0 ]; do
case "$1" in
-k|--kill)
ACTION="kill"
shift
if [ $# -gt 0 ] && [ "${1:0:1}" != "-" ]; then
SESSION_NAME="$1"; shift
fi
;;
-l|--list)
ACTION="list"; shift
;;
-a|--attach)
ACTION="attach"
shift
if [ $# -gt 0 ] && [ "${1:0:1}" != "-" ]; then
SESSION_NAME="$1"; shift
fi
;;
-h|--help)
ACTION="help"; shift
;;
-*)
echo "未知选项: $1" >&2; exit 1
;;
*)
SESSION_NAME="$1"; shift
;;
esac
done
SESSION_NAME="${SESSION_NAME:-$DEFAULT_SESSION}"
# ── 前置检查 ──────────────────────────────────────────────
if ! command -v tmux &>/dev/null; then
echo "错误: tmux 未安装" >&2
exit 1
fi
# ── 简单操作: 直接执行后退出 ──────────────────────────────
case "$ACTION" in
help)
echo "用法: bash $(basename "$0") [选项] [会话名称]"
echo ""
echo "选项:"
echo " (空) 创建默认会话 ($DEFAULT_SESSION)"
echo " <名称> 创建指定名称的会话"
echo " -k [名称] 杀死会话 (默认 $DEFAULT_SESSION)"
echo " -l, --list 列出所有 tmux 会话"
echo " -a [名称] 附加到已有会话"
echo " -h, --help 显示帮助信息"
exit 0
;;
list)
if tmux ls 2>/dev/null; then true; else echo "无活跃 tmux 会话"; fi
exit 0
;;
kill)
if tmux kill-session -t "$SESSION_NAME" 2>/dev/null; then
echo "会话 '$SESSION_NAME' 已终止"
exit 0
else
echo "会话 '$SESSION_NAME' 不存在" >&2
exit 1
fi
;;
attach)
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
exec tmux attach-session -t "$SESSION_NAME"
else
echo "会话 '$SESSION_NAME' 不存在" >&2
exit 1
fi
;;
esac
# ══════════════════════════════════════════════════════════
# 以下为 create 操作
# ══════════════════════════════════════════════════════════
# ── 基准比例 (源自 229×64 设计) ──────────────────────────
# Pane 1 宽度: 68/229 ≈ 30%
# Pane 4 高度: 20/64 ≈ 31%
# Pane 3 宽度: 79/161 ≈ 49% (占右区域)
# 使用先乘后除避免 bash 整数截断误差
BASE_W=229
BASE_H=64
BASE_P1_W=68
BASE_P4_H=20
BASE_P3_W=79
BASE_RIGHT_W=$((BASE_W - BASE_P1_W)) # 161
# ── 获取终端尺寸 ──────────────────────────────────────────
get_terminal_size() {
if [ -n "${TMUX:-}" ]; then
TERM_W=$(tmux display-message -p '#{window_width}')
TERM_H=$(tmux display-message -p '#{window_height}')
else
TERM_W=$(tput cols)
TERM_H=$(tput lines)
fi
}
# ── 等比例缩放计算 ───────────────────────────────────────
calc_proportions() {
local w="$1" h="$2"
PANE1_W=$((w * BASE_P1_W / BASE_W))
PANE4_H=$((h * BASE_P4_H / BASE_H))
local right_w=$((w - PANE1_W))
PANE3_W=$((right_w * BASE_P3_W / BASE_RIGHT_W))
# 保底最小值,防止极端小窗口出现 0 尺寸
PANE1_W=$((PANE1_W < 40 ? 40 : PANE1_W))
PANE4_H=$((PANE4_H < 8 ? 8 : PANE4_H))
PANE3_W=$((PANE3_W < 30 ? 30 : PANE3_W))
}
# ── 等待 shell 就绪并发送命令 ─────────────────────────────
# 等待 pane 中的 shell 完成初始化(出现提示符),再发送命令
# 解决: 在非 tmux 环境下,新 session 的 shell 可能未加载 .bashrc 别名
send_command_to_pane() {
local pane="$1"
local cmd="$2"
local max_wait=15
# 等待 shell 提示符出现(说明 .bashrc 已加载,别名可用)
for i in $(seq 1 "$max_wait"); do
if tmux capture-pane -t "$pane" -p 2>/dev/null | tail -3 \
| grep -qE '(\$|#||>)\s*$'; then
echo "[tmux-team] pane $pane shell 就绪 (等待 ${i}s)"
break
fi
sleep 0.5
done
# 分步发送: 先文本,再 Enter(避免 Enter 丢失)
tmux send-keys -t "$pane" "$cmd"
sleep 0.3
tmux send-keys -t "$pane" Enter
# 验证命令是否已提交
sleep 1
local verify="${cmd:0:10}"
for i in $(seq 1 3); do
local captured
captured=$(tmux capture-pane -t "$pane" -p | tail -10)
if echo "$captured" | grep -qF "$verify"; then
echo "[tmux-team] Enter 未生效,重试第 $i 次..."
tmux send-keys -t "$pane" Enter
sleep 1
else
return 0
fi
done
echo "[tmux-team] 警告: pane $pane 命令 '$cmd' 提交状态不确定"
}
# ── 构建布局 (通用函数) ───────────────────────────────────
# 参数: $1 = 初始 pane target, $2 = 宽, $3 = 高
build_layout() {
local base_pane="$1"
local w="$2" h="$3"
calc_proportions "$w" "$h"
# Step 1: 水平分屏 → Pane 1 (左) | 右区域
local right_id
right_id=$(tmux split-window -h -t "$base_pane" -c "$WORKSPACE" -P -F '#{pane_id}')
tmux resize-pane -t "$base_pane" -x "$PANE1_W"
# Step 2: 右区域垂直分出底部 Pane 4
local bottom_id
bottom_id=$(tmux split-window -v -t "$right_id" -l "$PANE4_H" -c "$WORKSPACE" -P -F '#{pane_id}')
# Step 3: 右上水平分为两列: Pane 2 | Pane 3
local right_col2_id
right_col2_id=$(tmux split-window -h -t "$right_id" -l "$PANE3_W" -c "$WORKSPACE" -P -F '#{pane_id}')
# Step 4: 底部水平分为两列: Pane 4 (测试) | Pane 5 (对话)
local monitor_id
monitor_id=$(tmux split-window -h -t "$bottom_id" -l "$PANE3_W" -c "$WORKSPACE" -P -F '#{pane_id}')
# ── 命名窗格 ──
tmux select-pane -t "$base_pane" -T "项目"
tmux select-pane -t "$right_id" -T "后端"
tmux select-pane -t "$right_col2_id" -T "前端"
tmux select-pane -t "$bottom_id" -T "测试"
tmux select-pane -t "$monitor_id" -T "对话"
# ── 启动 AI 编码工具(等待 shell 就绪后再发送,附带角色名称)──
local runner="${TOOL_RUNNER:-cc}"
send_command_to_pane "$base_pane" "${runner} --name \"项目\""
send_command_to_pane "$right_id" "${runner} --name \"后端\""
send_command_to_pane "$right_col2_id" "${runner} --name \"前端\""
send_command_to_pane "$bottom_id" "${runner} --name \"测试\""
# ── 发送角色提示词 ──
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# 根据 runner 类型选择提示词目录
if [ "$runner" = "oc" ]; then
PROMPT_DIR="$(dirname "$SCRIPT_DIR")/.opencode/agents"
PROMPT_FILES=("isos-pm.md" "isos-backend.md" "isos-frontend.md" "isos-test.md")
else
PROMPT_DIR="$(dirname "$SCRIPT_DIR")/.claude/agents"
PROMPT_FILES=("isos-project-manager-agent.md" "isos-backend-agent.md" "isos-frontend-agent.md" "isos-test-agent.md")
fi
# 并行发送,tmux-send-prompt.sh 会等待各工具就绪
bash "$SCRIPT_DIR/tmux-send-prompt.sh" "$base_pane" "${PROMPT_DIR}/${PROMPT_FILES[0]}" &
bash "$SCRIPT_DIR/tmux-send-prompt.sh" "$right_id" "${PROMPT_DIR}/${PROMPT_FILES[1]}" &
bash "$SCRIPT_DIR/tmux-send-prompt.sh" "$right_col2_id" "${PROMPT_DIR}/${PROMPT_FILES[2]}" &
bash "$SCRIPT_DIR/tmux-send-prompt.sh" "$bottom_id" "${PROMPT_DIR}/${PROMPT_FILES[3]}" &
wait
# ── 启动 cc 实时监控 (对话 pane) ──
sleep 1
tmux send-keys -t "$monitor_id" "bash ${WORKSPACE}/scripts/tmux-cc-monitor.sh watch ${base_pane} ${right_id} ${right_col2_id} ${bottom_id}" Enter
# 选中主 Agent 窗格
tmux select-pane -t "$base_pane"
}
# ── 输出信息 ──────────────────────────────────────────────
print_info() {
echo ""
echo "窗格布局:"
echo " +----------+--------------------+--------------------+"
echo " | | Pane 2 (后端) | Pane 3 (前端) |"
echo " | Pane 1 +--------------------+--------------------+"
echo " | (项目) | Pane 4 (测试) | Pane 5 (对话) |"
echo " | | | |"
echo " +----------+--------------------+--------------------+"
echo ""
echo "角色分配:"
echo " Pane 1: 项目 — 项目管理、任务协调"
echo " Pane 2: 后端 — 服务端开发 (FastAPI + SQLite)"
echo " Pane 3: 前端 — 桌面端开发 (Svelte 5 + PyWebView)"
echo " Pane 4: 测试 — 全级别测试和覆盖率"
echo " Pane 5: 对话 — 实时监控各面板 cc 对话"
echo ""
echo "所有窗格已启动 AI 编码工具并加载角色提示词"
}
# ── 创建布局 ──────────────────────────────────────────────
if [ -n "${TMUX:-}" ]; then
# ── 情况 1: 已在 tmux 中 → 重命名会话并在当前窗口构建布局 ──
tmux rename-session -t "$(tmux display-message -p '#{session_name}')" "$SESSION_NAME" 2>/dev/null || true
tmux rename-window -t "$(tmux display-message -p '#{window_id}')" "ISOS-Team" 2>/dev/null || true
current_pane=$(tmux display-message -p '#{pane_id}')
# 检查当前窗口是否已有多个 pane
pane_count=$(tmux list-panes -t "$(tmux display-message -p '#{window_id}')" | wc -l)
if [ "$pane_count" -gt 1 ]; then
echo "当前窗口已有 ${pane_count} 个窗格,跳过布局创建"
echo "请先关闭多余窗格或切换到新窗口后再试"
exit 1
fi
get_terminal_size
build_layout "$current_pane" "$TERM_W" "$TERM_H"
echo "团队工作空间已在当前窗口创建 (${TERM_W}×${TERM_H})"
print_info
else
# ── 情况 2: 不在 tmux 中 → 创建新会话并附加 ──
# 如果会话已存在,直接附加
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "会话 '$SESSION_NAME' 已存在,附加..."
exec tmux attach-session -t "$SESSION_NAME"
fi
get_terminal_size
# 创建新会话 (需要短暂后台创建以构建布局)
tmux new-session -d -s "$SESSION_NAME" -c "$WORKSPACE"
tmux rename-window -t "$SESSION_NAME:1" "ISOS-Team"
build_layout "$SESSION_NAME:1.1" "$TERM_W" "$TERM_H"
echo "团队工作空间 '$SESSION_NAME' 已创建 (${TERM_W}×${TERM_H})"
print_info
if [ "${OPENCODE:-}" = "1" ] || [ "${CLAUDECODE:-}" = "1" ]; then
# Claude Code 环境中无法 attach,仅打印提示
echo "提示: 在 AI 编码工具中执行,跳过 attach"
echo "请在终端中运行: tmux attach -t $SESSION_NAME"
else
# 用户终端中直接执行,自动 attach
echo "正在附加到会话..."
exec tmux attach-session -t "$SESSION_NAME"
fi
fi