#!/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