Skip to content

Commit b3d5033

Browse files
meme-dayoclaude
andcommitted
feat: tmux監視によるカスタムhooks実装 (Issue #45)
CLI_HOOKS_MODE=customで公式hooks代替を提供 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5c82688 commit b3d5033

File tree

5 files changed

+221
-1
lines changed

5 files changed

+221
-1
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,19 @@ VIBECODE_ENABLE_TELEMETRY=false ./start_PM.sh
654654

655655
⚠️ hooks無効化は非推奨 - ポーリング型エージェントが待機状態に入りプロジェクト未達成のまま終了するリスク大
656656

657+
#### カスタム監視モード(v0.7+)
658+
659+
公式hooks不安定時の代替として、tmux監視による状態検出を提供します。
660+
661+
**有効化方法**:
662+
```bash
663+
export CLI_HOOKS_MODE=custom
664+
./communication/setup.sh 12
665+
```
666+
667+
- **機能**: Stop/SessionStart/PostToolUse hooksをtmux capture-pane経由で実現
668+
- **詳細**: [Issue #45](https://github.com/Katagiri-Hoshino-Lab/VibeCodeHPC-jp/issues/45)
669+
657670
詳細は `hooks/hooks_deployment_guide.md` を参照してください。
658671

659672

communication/start_agent.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ echo "🚀 Starting agent $AGENT_ID at $TARGET_DIR"
4949
# 1. プロジェクトルートを環境変数として設定
5050
./communication/agent_send.sh "$AGENT_ID" "export VIBECODE_ROOT='$PROJECT_ROOT'"
5151

52+
# CLI_HOOKS_MODEを環境変数として設定(未設定時はauto)
53+
CLI_HOOKS_MODE="${CLI_HOOKS_MODE:-auto}"
54+
./communication/agent_send.sh "$AGENT_ID" "export CLI_HOOKS_MODE='$CLI_HOOKS_MODE'"
55+
5256
# 2. ターゲットディレクトリに移動
5357
# TARGET_DIRが絶対パスか相対パスかを判定
5458
if [[ "$TARGET_DIR" = /* ]]; then

hooks/setup_agent_hooks.sh

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ fi
3030

3131
echo "🔧 Setting up hooks for agent: $AGENT_ID (type: $AGENT_TYPE, version: $HOOKS_VERSION)"
3232

33+
# CLI_HOOKS_MODEを取得(環境変数から)
34+
CLI_HOOKS_MODE="${CLI_HOOKS_MODE:-auto}"
35+
echo " CLI_HOOKS_MODE: $CLI_HOOKS_MODE"
36+
3337
# .claude/hooks ディレクトリ作成
3438
mkdir -p "$AGENT_DIR/.claude/hooks"
3539

@@ -45,7 +49,40 @@ echo "$AGENT_ID" > "$AGENT_DIR/.claude/hooks/agent_id.txt"
4549
# エージェントタイプに応じたstop hookをコピー
4650
# v0.4以降:PGもポーリング型に変更(全エージェントがポーリング型)
4751
# v0.5: SOLOエージェントもv3を使用(auto_tuning_config.json活用)
48-
if [ "$AGENT_ID" = "SOLO" ]; then
52+
53+
# CLI_HOOKS_MODE=customの場合は、hooksセクションのみ空にする
54+
if [ "$CLI_HOOKS_MODE" = "custom" ]; then
55+
echo " Custom hooks mode: hooks section will be empty"
56+
# stop.pyはコピーするが、settings.local.jsonには登録しない
57+
if [ "$AGENT_TYPE" = "polling" ] || [[ "$AGENT_ID" =~ ^PG ]] || [ "$AGENT_ID" = "SOLO" ]; then
58+
cp "$TEMPLATE_DIR/stop_polling_v3.py" "$AGENT_DIR/.claude/hooks/stop.py"
59+
else
60+
cp "$TEMPLATE_DIR/stop_event.py" "$AGENT_DIR/.claude/hooks/stop.py"
61+
fi
62+
63+
# 既存のsettings.local.jsonがあればhooksセクションのみ削除、なければ新規作成
64+
if [ -f "$AGENT_DIR/.claude/settings.local.json" ]; then
65+
if command -v jq &> /dev/null; then
66+
jq '. + {"hooks": {}}' "$AGENT_DIR/.claude/settings.local.json" > "$AGENT_DIR/.claude/settings.local.json.tmp"
67+
mv "$AGENT_DIR/.claude/settings.local.json.tmp" "$AGENT_DIR/.claude/settings.local.json"
68+
else
69+
echo "⚠️ jq not found, cannot preserve existing settings"
70+
cat > "$AGENT_DIR/.claude/settings.local.json" << EOF
71+
{
72+
"hooks": {}
73+
}
74+
EOF
75+
fi
76+
else
77+
cat > "$AGENT_DIR/.claude/settings.local.json" << EOF
78+
{
79+
"hooks": {}
80+
}
81+
EOF
82+
fi
83+
echo "✅ Custom hooks mode configured (hooks will be called by state monitor)"
84+
85+
elif [ "$AGENT_ID" = "SOLO" ]; then
4986
# SOLOもstop_polling_v3.pyを使用(SOLOの確率設定あり)
5087
cp "$TEMPLATE_DIR/stop_polling_v3.py" "$AGENT_DIR/.claude/hooks/stop.py"
5188
# settings.jsonを作成(SOLOも同じ構造)

telemetry/launch_claude_with_env.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ if [ $? -ne 0 ]; then
114114
exit 1
115115
fi
116116

117+
# カスタム監視モード時にstate_monitorを起動
118+
HOOKS_MODE="${CLI_HOOKS_MODE:-auto}"
119+
if [ "$HOOKS_MODE" = "custom" ] || [ "$HOOKS_MODE" = "hybrid" ]; then
120+
if [ -n "$TMUX_PANE" ]; then
121+
echo "🔍 Starting state monitor for $AGENT_ID (pane: $TMUX_PANE)"
122+
"$TELEMETRY_DIR/state_monitor_tmux.sh" "$AGENT_ID" "$TMUX_PANE" > /dev/null 2>&1 &
123+
MONITOR_PID=$!
124+
echo " Monitor PID: $MONITOR_PID"
125+
else
126+
echo "⚠️ Warning: Not in tmux pane, custom hooks monitoring disabled"
127+
fi
128+
fi
129+
117130
# Claude Codeを起動
118131
echo "Starting claude with options: --dangerously-skip-permissions $@"
119132
echo "Current directory: $CURRENT_DIR"

telemetry/state_monitor_tmux.sh

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/bin/bash
2+
# tmuxベースの状態監視スクリプト(公式hooks代替)
3+
4+
if [ $# -lt 2 ]; then
5+
echo "Usage: $0 <AGENT_ID> <PANE_ID>"
6+
exit 1
7+
fi
8+
9+
AGENT_ID=$1
10+
PANE_ID=$2
11+
12+
# プロジェクトルート
13+
if [ -n "$VIBECODE_ROOT" ]; then
14+
PROJECT_ROOT="$VIBECODE_ROOT"
15+
else
16+
PROJECT_ROOT="$(pwd)"
17+
fi
18+
19+
# 状態管理
20+
STATE="idle"
21+
PENDING_STATE="idle"
22+
PENDING_START=0
23+
STATE_PERSISTENCE_MS=200
24+
25+
# session_id取得(agent_and_pane_id_table.jsonlから)
26+
get_session_id() {
27+
if [ -f "$PROJECT_ROOT/Agent-shared/agent_and_pane_id_table.jsonl" ]; then
28+
grep "\"agent_id\":\"$AGENT_ID\"" "$PROJECT_ROOT/Agent-shared/agent_and_pane_id_table.jsonl" | \
29+
tail -1 | \
30+
grep -oP '"claude_session_id":"\K[^"]*'
31+
fi
32+
}
33+
34+
# 状態検出
35+
detect_state() {
36+
local content="$1"
37+
local content_lower=$(echo "$content" | tr '[:upper:]' '[:lower:]')
38+
39+
# waiting
40+
if echo "$content" | grep -qE "│ do you want|│ would you like"; then
41+
echo "waiting"
42+
return
43+
fi
44+
45+
# busy
46+
if echo "$content_lower" | grep -q "esc to interrupt"; then
47+
echo "busy"
48+
return
49+
fi
50+
51+
echo "idle"
52+
}
53+
54+
# 状態変化時の処理
55+
on_state_changed() {
56+
local old_state="$1"
57+
local new_state="$2"
58+
59+
# Stopイベント相当(busy/waiting → idle)
60+
if [ "$new_state" = "idle" ] && [ "$old_state" != "idle" ]; then
61+
if [ -f ".claude/hooks/stop.py" ]; then
62+
echo "[state_monitor] Calling Stop hook"
63+
echo '{}' | python3 ".claude/hooks/stop.py" 2>/dev/null || true
64+
fi
65+
fi
66+
}
67+
68+
echo "[state_monitor] Starting for $AGENT_ID (pane: $PANE_ID)"
69+
70+
# SessionStart hook実行(起動時1回のみ)
71+
if [ -f ".claude/hooks/session_start.py" ]; then
72+
echo "[state_monitor] Calling SessionStart hook"
73+
echo '{}' | python3 ".claude/hooks/session_start.py" 2>/dev/null || true
74+
fi
75+
76+
# PostToolUse監視用の最終チェック時刻
77+
LAST_TOOL_CHECK=0
78+
79+
# JSONL監視用の関数
80+
check_post_tool_use() {
81+
# session_idを取得
82+
local session_id=$(get_session_id)
83+
if [ -z "$session_id" ]; then
84+
return
85+
fi
86+
87+
# JSONLパスを構築(~/.claude/projects/プロジェクトスラグ/session_id.jsonl)
88+
local home_dir=$(eval echo ~$(whoami))
89+
local jsonl_pattern="$home_dir/.claude/projects/*/${session_id}.jsonl"
90+
local jsonl_file=$(ls $jsonl_pattern 2>/dev/null | head -1)
91+
92+
if [ ! -f "$jsonl_file" ]; then
93+
return
94+
fi
95+
96+
# 最新10行をチェック(tool_useイベントを探す)
97+
local recent_tools=$(tail -10 "$jsonl_file" 2>/dev/null | grep -o '"type":"tool_use".*"name":"Bash"' | tail -1)
98+
99+
if [ -n "$recent_tools" ]; then
100+
# Bash tool_useが見つかった場合、post_tool_ssh_handler.pyを呼ぶ
101+
if [ -f ".claude/hooks/post_tool_ssh_handler.py" ]; then
102+
echo "[state_monitor] Detected Bash tool use, calling PostToolUse hook"
103+
# 最新のtool_use行全体を取得してstdinに渡す
104+
tail -10 "$jsonl_file" 2>/dev/null | grep '"type":"tool_use"' | tail -1 | python3 ".claude/hooks/post_tool_ssh_handler.py" 2>/dev/null || true
105+
fi
106+
fi
107+
}
108+
109+
# メインループ
110+
while true; do
111+
# tmux pane存在確認
112+
if ! tmux list-panes -t "$PANE_ID" >/dev/null 2>&1; then
113+
echo "[state_monitor] Pane $PANE_ID not found, exiting"
114+
break
115+
fi
116+
117+
# 最新30行取得
118+
OUTPUT=$(tmux capture-pane -p -S -30 -E -1 -t "$PANE_ID" 2>/dev/null || echo "")
119+
120+
if [ -n "$OUTPUT" ]; then
121+
# 状態検出
122+
DETECTED=$(detect_state "$OUTPUT")
123+
NOW=$(date +%s%3N 2>/dev/null || echo "0")
124+
125+
# PostToolUse監視(5秒ごとにチェック)
126+
if [ "$NOW" != "0" ]; then
127+
if [ $((NOW - LAST_TOOL_CHECK)) -ge 5000 ]; then
128+
check_post_tool_use
129+
LAST_TOOL_CHECK=$NOW
130+
fi
131+
fi
132+
133+
# デバウンス処理
134+
if [ "$DETECTED" != "$PENDING_STATE" ]; then
135+
PENDING_STATE="$DETECTED"
136+
PENDING_START=$NOW
137+
else
138+
if [ "$NOW" != "0" ]; then
139+
DURATION=$((NOW - PENDING_START))
140+
if [ $DURATION -ge $STATE_PERSISTENCE_MS ] && [ "$STATE" != "$DETECTED" ]; then
141+
OLD_STATE="$STATE"
142+
STATE="$DETECTED"
143+
echo "[state_monitor] State changed: $OLD_STATE$STATE"
144+
on_state_changed "$OLD_STATE" "$STATE"
145+
fi
146+
fi
147+
fi
148+
fi
149+
150+
sleep 0.1
151+
done
152+
153+
echo "[state_monitor] Stopped for $AGENT_ID"

0 commit comments

Comments
 (0)