diff --git a/.claude/commands/isos-tmux-team.md b/.claude/commands/isos-tmux-team.md index 7175568..e0a8596 100644 --- a/.claude/commands/isos-tmux-team.md +++ b/.claude/commands/isos-tmux-team.md @@ -1,6 +1,6 @@ --- name: 团队工作空间 -description: 启动 tmux 4 Pane 团队工作空间,命名所有层级并自动加载角色提示词 +description: 启动 tmux 5 Pane 团队工作空间,命名所有层级并自动加载角色提示词 --- ## 用户输入 @@ -34,18 +34,19 @@ $ARGUMENTS | Pane 2 | `后端` | 后端开发 | | Pane 3 | `前端` | 前端开发 | | Pane 4 | `测试` | 测试工程师 | +| Pane 5 | `对话` | cc 对话监控 | ## 布局说明 -使用 `/workspace/scripts/tmux-team.sh` 创建以下 4 Pane 布局: +使用 `/workspace/scripts/tmux-team.sh` 创建以下 5 Pane 布局: ``` -+----------+------------------------------------------+ -| | Pane 2 (后端) | Pane 3 (前端) | -| Pane 1 +------------------------------------------+ -| (PM) | Pane 4 (测试) | -| | | -+----------+------------------------------------------+ ++----------+--------------------+--------------------+ +| | Pane 2 (后端) | Pane 3 (前端) | +| Pane 1 +--------------------+--------------------+ +| (PM) | Pane 4 (测试) | Pane 5 (对话) | +| | | | ++----------+--------------------+--------------------+ ``` | Pane | 窗格标题 | 角色 | 提示词文件 | 用途 | @@ -54,6 +55,7 @@ $ARGUMENTS | 2 | 后端 | 后端开发 | `.claude/agents/isos-backend-agent.md` | 服务端开发 | | 3 | 前端 | 前端开发 | `.claude/agents/isos-frontend-agent.md` | 桌面端开发 | | 4 | 测试 | 测试工程师 | `.claude/agents/isos-test-agent.md` | 全级别测试和覆盖率 | +| 5 | 对话 | cc 监控 | — | 实时监控 项目/后端/前端/测试 的 cc 对话 | ## 执行步骤 @@ -115,6 +117,7 @@ tmux list-windows -t [会话名称] -F "Window #{window_index}: #{window_name}" | 2 | 后端 | 后端开发 | {size} | (x,y) | {cmd} | | 3 | 前端 | 前端开发 | {size} | (x,y) | {cmd} | | 4 | 测试 | 测试工程师 | {size} | (x,y) | {cmd} | +| 5 | 对话 | cc 监控 | {size} | (x,y) | bash | ### 快速操作 diff --git a/member/vscode.chat.md b/member/vscode.chat.md index 4ec762f..79b7818 100644 --- a/member/vscode.chat.md +++ b/member/vscode.chat.md @@ -1 +1 @@ -# vscode 的对话记录 +# vscode 的对话记录 \ No newline at end of file diff --git a/scripts/tmux-cc-monitor.sh b/scripts/tmux-cc-monitor.sh new file mode 100755 index 0000000..799de00 --- /dev/null +++ b/scripts/tmux-cc-monitor.sh @@ -0,0 +1,213 @@ +#!/bin/bash +# tmux-cc-monitor.sh — 实时监控 tmux pane 中 cc (Claude Code) 的消息流 +# +# 用法: +# tmux-cc-monitor.sh watch %1 [%2 ...] # 监控指定 pane +# tmux-cc-monitor.sh watch-all # 监控当前 session 所有 pane +# tmux-cc-monitor.sh help +# +# 工作原理: +# 1. 每 200ms 轮询 tmux capture-pane 获取 pane 内容 +# 2. 用内容 hash 跟踪已显示行,仅显示新增内容 +# 3. 根据 ❯ 标记区分用户输入和模型输出 +# 4. 彩色流式显示到当前终端 + +set -euo pipefail + +POLL_INTERVAL=0.2 + +# Colors +C_INPUT='\033[1;32m' +C_OUTPUT='\033[0;36m' +C_SYSTEM='\033[0;33m' +C_RESET='\033[0m' + +ts() { date +%H:%M:%S; } + +strip_ansi() { + sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*m//g; s/\r$//g' +} + +is_blank() { + local s="$1" + [[ -z "${s//[[:space:]]/}" ]] +} + +# ─── 噪声过滤 ─── +is_noise() { + local line="$1" + local trimmed + trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + [[ -z "$trimmed" ]] && return 0 + + # 状态栏 + [[ "$trimmed" =~ ^[─═]+$ ]] && return 0 + [[ "$trimmed" =~ 版本.*模型 ]] && return 0 + [[ "$trimmed" =~ bypass ]] && return 0 + [[ "$trimmed" =~ ^─+.*──[[:space:]]*$ ]] && return 0 + + # cc 加载动画 (含省略号 … 的行) + [[ "$trimmed" == *…* ]] && return 0 + + # cc 命令状态 + [[ "$trimmed" =~ Running\ [0-9]+\ bash\ command ]] && return 0 + [[ "$trimmed" =~ Ran\ [0-9]+\ bash\ command ]] && return 0 + + return 1 +} + +# ─── 行级去重 ─── +# 用 (行内容hash) 跟踪已显示的行,避免重复输出 +# 维护一个固定大小的环状缓冲区 +SEEN_CAPACITY=500 +declare -A SEEN_LINES=() +SEEN_ORDER=() +SEEN_POS=0 + +# 记录一行已显示,返回 true 如果是新行 +mark_seen() { + local line="$1" + local hash + hash=$(echo "$line" | md5sum | cut -d' ' -f1) + + if [[ -n "${SEEN_LINES[$hash]+x}" ]]; then + return 1 # 已存在 + fi + + SEEN_LINES["$hash"]=1 + SEEN_ORDER+=("$hash") + + # 裁剪缓冲区 + if [[ ${#SEEN_ORDER[@]} -gt $SEEN_CAPACITY ]]; then + local old_hash="${SEEN_ORDER[0]}" + SEEN_ORDER=("${SEEN_ORDER[@]:1}") + unset 'SEEN_LINES[$old_hash]' + fi + + return 0 +} + +# ─── 单 Pane 监控 ─── + +watch_pane() { + local pane_id="$1" + local state="idle" + local last_input="" + + # 初始化: 将当前所有行标记为已读 + local init_content + init_content=$(tmux capture-pane -t "$pane_id" -p 2>/dev/null || echo "") + while IFS= read -r line; do + is_blank "$line" || mark_seen "$line" || true + done <<< "$(echo "$init_content" | strip_ansi)" + + echo -e "${C_SYSTEM}[$(ts)][${pane_id}] 开始监控${C_RESET}" + + local prev_content="$init_content" + + while true; do + local content + content=$(tmux capture-pane -t "$pane_id" -p 2>/dev/null) || { + sleep 0.5 + continue + } + + # 快速检测: hash + local prev_hash curr_hash + prev_hash=$(echo "$prev_content" | md5sum) + curr_hash=$(echo "$content" | md5sum) + [[ "$curr_hash" == "$prev_hash" ]] && { sleep "$POLL_INTERVAL"; continue; } + + # 清洗内容 + local clean_curr + clean_curr=$(echo "$content" | strip_ansi) + + # ── 找出所有新行(基于行级去重)── + while IFS= read -r line; do + is_blank "$line" && continue + is_noise "$line" && continue + + # 检查是否是新行 + if ! mark_seen "$line"; then + continue + fi + + # ── 分类 ── + if echo "$line" | grep -q '❯'; then + local input_text + input_text=$(echo "$line" | sed 's/.*❯//' | tr -d '\302\240' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + # 去重: 用 trim 后的文本比较,避免前导空格导致的重复 + if [[ -n "$input_text" && "$input_text" != "$last_input" ]]; then + echo -e "${C_INPUT}[$(ts)][${pane_id}][输入]${C_RESET} ${input_text}" + last_input="$input_text" + state="output" + elif [[ -z "$input_text" ]]; then + state="idle" + last_input="" + fi + else + if [[ "$state" == "output" ]]; then + echo -e "${C_OUTPUT}[$(ts)][${pane_id}][输出]${C_RESET} ${line}" + fi + fi + done <<< "$clean_curr" + + prev_content="$content" + sleep "$POLL_INTERVAL" + done +} + +# ─── Main ─── + +cmd="${1:-help}" +shift || true + +case "$cmd" in + watch) + [[ $# -eq 0 ]] && { echo "用法: tmux-cc-monitor.sh watch [pane_id ...]"; exit 1; } + + pids=() + for pane_id in "$@"; do + ( + # 每个 pane 有独立的去重缓冲区 + SEEN_LINES=() + SEEN_ORDER=() + watch_pane "$pane_id" + ) & + pids+=($!) + done + + cleanup() { + for pid in "${pids[@]}"; do + kill "$pid" 2>/dev/null || true + done + } + trap cleanup INT TERM + echo -e "${C_SYSTEM}[$(ts)] 监控已启动 (PIDs: ${pids[*]}),Ctrl+C 停止${C_RESET}" + wait + ;; + + watch-all) + panes=$(tmux list-panes -F '#{pane_id}' 2>/dev/null || true) + [[ -z "$panes" ]] && { echo "错误: 未找到 tmux panes"; exit 1; } + exec "$0" watch $panes + ;; + + help|--help|-h) + echo "用法: tmux-cc-monitor.sh " + echo "" + echo "命令:" + echo " watch 监控指定 pane 的 cc 消息流" + echo " watch-all 监控当前 session 所有 pane" + echo "" + echo "输出:" + echo -e " ${C_INPUT}[时间][pane][输入]${C_RESET} 用户提示词" + echo -e " ${C_OUTPUT}[时间][pane][输出]${C_RESET} 模型响应(流式)" + ;; + + *) + echo "未知命令: $cmd" >&2 + exit 1 + ;; +esac diff --git a/scripts/tmux-team.sh b/scripts/tmux-team.sh index 41b4a21..0b22b86 100755 --- a/scripts/tmux-team.sh +++ b/scripts/tmux-team.sh @@ -14,12 +14,12 @@ # - 不在 tmux 中: 启动新 tmux 会话并附加 # # 视觉布局: -# +----------+------------------------------------------+ -# | | Pane 2 (后端) | Pane 3 (前端) | -# | Pane 1 +------------------------------------------+ -# | (项目) | Pane 4 (测试) | -# | | | -# +----------+------------------------------------------+ +# +----------+--------------------+--------------------+ +# | | Pane 2 (后端) | Pane 3 (前端) | +# | Pane 1 +--------------------+--------------------+ +# | (项目) | Pane 4 (测试) | Pane 5 (对话) | +# | | | | +# +----------+--------------------+--------------------+ set -euo pipefail @@ -216,11 +216,16 @@ build_layout() { 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}" @@ -248,6 +253,10 @@ build_layout() { 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" } @@ -257,18 +266,19 @@ build_layout() { print_info() { echo "" echo "窗格布局:" - echo " +----------+------------------------------------------+" - echo " | | Pane 2 (后端) | Pane 3 (前端) |" - echo " | Pane 1 +------------------------------------------+" - echo " | (项目) | Pane 4 (测试) |" - 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 编码工具并加载角色提示词" }