#!/bin/bash # tmux-msg-log.sh — 消息日志查询工具 # # 用法: # tmux-msg-log.sh recent [--count 20] # tmux-msg-log.sh by-pane # tmux-msg-log.sh show # tmux-msg-log.sh stats # tmux-msg-log.sh log "" # # 示例: # tmux-msg-log.sh recent # tmux-msg-log.sh by-pane %2 # tmux-msg-log.sh show msg-20260419-142357-a1b2 # tmux-msg-log.sh stats set -euo pipefail MSG_DIR="${TMUX_MSG_DIR:-/workspace/.tmux-messages}" INDEX_FILE="${MSG_DIR}/index.jsonl" # ─── 颜色 ─── GREEN='\033[32m' RED='\033[31m' YELLOW='\033[33m' CYAN='\033[36m' BOLD='\033[1m' RESET='\033[0m' read_envelope() { local msg_id="$1" find "$MSG_DIR" -name "${msg_id}.json" -type f 2>/dev/null | head -1 } # ─── recent 子命令 ─── cmd_recent() { local count="${1:-20}" if [[ ! -f "$INDEX_FILE" ]]; then echo "暂无消息记录" return fi echo -e "${BOLD}══ 最近 ${count} 条消息 ══${RESET}" echo "" tail -n "$count" "$INDEX_FILE" | while IFS= read -r line; do [[ -z "$line" ]] && continue echo "$line" | python3 -c " import sys, json d = json.loads(sys.stdin.read()) direction = d.get('direction', '?') status = d.get('status', '?') msg_id = d.get('msg_id', '?') from_p = d.get('from', '?') to_p = d.get('to', '?') summary = d.get('summary', '')[:50] ts = d.get('ts', '?') # 状态颜色 if status in ('completed', 'delivered', 'replied'): color = '\033[32m' elif status in ('failed', 'send_failed', 'reply_failed'): color = '\033[31m' else: color = '\033[33m' direction_icon = '→' if direction == 'request' else '←' print(f' {color}{status:12}\033[0m {msg_id} {direction_icon} {from_p}→{to_p}') print(f' {summary}') print(f' {ts}') print() " 2>/dev/null done } # ─── by-pane 子命令 ─── cmd_by_pane() { local pane_id="${1:?用法: tmux-msg-log.sh by-pane }" if [[ ! -f "$INDEX_FILE" ]]; then echo "暂无消息记录" return fi echo -e "${BOLD}══ Pane ${pane_id} 的消息 ══${RESET}" local count=0 while IFS= read -r line; do [[ -z "$line" ]] && continue local match match=$(echo "$line" | python3 -c " import sys, json d = json.loads(sys.stdin.read()) if d.get('from') == '${pane_id}' or d.get('to') == '${pane_id}': print('yes') else: print('no') " 2>/dev/null) [[ "$match" != "yes" ]] && continue count=$((count + 1)) echo "$line" | python3 -c " import sys, json d = json.loads(sys.stdin.read()) direction = d.get('direction', '?') status = d.get('status', '?') msg_id = d.get('msg_id', '?') from_p = d.get('from', '?') to_p = d.get('to', '?') summary = d.get('summary', '')[:50] role = '发送' if d.get('from') == '${pane_id}' else '接收' print(f' [{role}] {status:12} {msg_id} {summary}') " 2>/dev/null done < "$INDEX_FILE" echo "" echo "共 ${count} 条消息" } # ─── show 子命令 ─── cmd_show() { local msg_id="${1:?用法: tmux-msg-log.sh show }" local envelope_path envelope_path=$(read_envelope "$msg_id") if [[ -z "$envelope_path" ]]; then echo "错误: 未找到消息 ${msg_id}" >&2 exit 1 fi python3 -c " import sys, json with open('${envelope_path}') as f: d = json.load(f) print('══ 消息详情 ══') print(f'消息ID: {d[\"msg_id\"]}') print(f'方向: {d[\"direction\"]}') if d.get('in_reply_to'): print(f'回复消息: {d[\"in_reply_to\"]}') print(f'来源: {d[\"from_title\"]} ({d[\"from_pane\"]})') print(f'目标: {d[\"to_title\"]} ({d[\"to_pane\"]})') print(f'状态: {d[\"status\"]}') if d.get('task_summary'): print(f'任务摘要: {d[\"task_summary\"]}') if d.get('task_file'): print(f'任务文件: {d[\"task_file\"]}') print(f'创建时间: {d[\"created_at\"]}') if d.get('delivered_at'): print(f'送达时间: {d[\"delivered_at\"]}') if d.get('replied_at'): print(f'回复时间: {d[\"replied_at\"]}') print(f'回复摘要: {d.get(\"reply_summary\", \"\")}') if d.get('error'): print(f'错误: {d[\"error\"]}') " 2>/dev/null } # ─── stats 子命令 ─── cmd_stats() { if [[ ! -f "$INDEX_FILE" ]]; then echo "暂无消息记录" return fi local total=0 requests=0 replies=0 local completed=0 pending=0 failed=0 declare -A pane_counts while IFS= read -r line; do [[ -z "$line" ]] && continue total=$((total + 1)) local direction status from to direction=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('direction','?'))" 2>/dev/null) status=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('status','?'))" 2>/dev/null) from=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('from','?'))" 2>/dev/null) to=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('to','?'))" 2>/dev/null) [[ "$direction" == "request" ]] && requests=$((requests + 1)) [[ "$direction" == "reply" ]] && replies=$((replies + 1)) case "$status" in completed|delivered|replied) completed=$((completed + 1)) ;; created|sent|pending) pending=$((pending + 1)) ;; failed|send_failed|reply_failed) failed=$((failed + 1)) ;; esac pane_counts["$from"]=$(( ${pane_counts["$from"]:-0} + 1 )) pane_counts["$to"]=$(( ${pane_counts["$to"]:-0} + 1 )) done < "$INDEX_FILE" echo -e "${BOLD}══ 消息统计 ══${RESET}" echo "" echo " 总计: ${total}" echo " 请求: ${requests}" echo " 回复: ${replies}" echo "" echo -e " ${GREEN}完成: ${completed}${RESET} ${YELLOW}待处理: ${pending}${RESET} ${RED}失败: ${failed}${RESET}" echo "" echo "各面板活跃度:" for pane in "${!pane_counts[@]}"; do printf " %-6s %d 条\n" "$pane" "${pane_counts[$pane]}" done } # ─── 主入口 ─── # ─── list 子命令 ─── cmd_list() { local filter_from='' filter_to='' filter_status='' while [[ $# -gt 0 ]]; do case "$1" in --from) filter_from="$2"; shift 2 ;; --to) filter_to="$2"; shift 2 ;; --status) filter_status="$2"; shift 2 ;; *) shift ;; esac done if [[ ! -f "$INDEX_FILE" ]]; then echo "暂无消息记录" return fi local count=0 while IFS= read -r line; do [[ -z "$line" ]] && continue if [[ -n "$filter_from" ]]; then local from from=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['from'])" 2>/dev/null) [[ "$from" != "$filter_from" ]] && continue fi if [[ -n "$filter_to" ]]; then local to to=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['to'])" 2>/dev/null) [[ "$to" != "$filter_to" ]] && continue fi if [[ -n "$filter_status" ]]; then local st st=$(echo "$line" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['status'])" 2>/dev/null) [[ "$st" != "$filter_status" ]] && continue fi count=$((count + 1)) echo "$line" | python3 -c " import sys, json d = json.loads(sys.stdin.read()) print(f'{d[\"msg_id\"]} {d[\"status\"]:12} {d[\"from\"]}→{d[\"to\"]} {d.get(\"summary\",\"\")[:50]}') " 2>/dev/null done < "$INDEX_FILE" echo "" echo "共 ${count} 条消息" } # ─── 主入口 ─── cmd="${1:-help}" shift || true case "$cmd" in recent) cmd_recent "$@" ;; by-pane) cmd_by_pane "$@" ;; show) cmd_show "$@" ;; stats) cmd_stats ;; list) cmd_list "$@" ;; help|--help|-h) echo "用法: tmux-msg-log.sh [options]" echo "" echo "命令:" echo " recent [--count N] 最近 N 条消息(默认 20)" echo " by-pane 指定面板的消息" echo " show 消息详情" echo " list [--from

] [--to

] [--status ]" echo " 列出消息(可过滤)" echo " stats 统计摘要" ;; *) echo "错误: 未知命令 '$cmd'" >&2 exit 1 ;; esac