#!/usr/bin/env node /** * PostToolUse Hook: 会话心跳检测器 * Matcher: Edit|Write|Skill|Agent|Bash|mcp__.* * * 累计当前会话的工具调用次数,在达到阈值时通过 systemMessage * 提醒 Claude 建议用户 /clear 重置上下文,防止上下文爆窗。 * * 阈值策略 (渐进式提醒,避免通知轰炸): * 20 次 — 轻提醒 (INFO) * 30 次 — 中提醒 (WARNING) * 40 次 — 强提醒 (CRITICAL) * 50+ 次 — 每 10 次重复强提醒 * * 会话判定: 超过 30 分钟无工具调用 → 视为新会话,计数器归零。 * * 退出码: 始终 0 (纯通知,不阻断工作流) * Fail-open: 任何异常 → exit(0) */ 'use strict'; const fs = require('fs'); const path = require('path'); const readStdin = require('./lib/read-stdin.js'); const CLAUDE_ROOT = require('./lib/root.js'); const STATE_FILE = path.join(CLAUDE_ROOT, 'debug', 'session-heartbeat.json'); const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 分钟无活动 = 新会话 // 阈值与提醒级别 const THRESHOLDS = [ { count: 20, level: 'INFO', emoji: '', msg: '当前会话已执行 {n} 次工具调用。如对话较长,可考虑 /clear 释放上下文。' }, { count: 30, level: 'WARNING', emoji: '', msg: '当前会话已执行 {n} 次工具调用,上下文可能接近饱和。建议在合适时机 /clear 重置上下文窗口。' }, { count: 40, level: 'CRITICAL', emoji: '', msg: '当前会话已执行 {n} 次工具调用,上下文压力较大。强烈建议用户 /clear 重置。如有重要任务进行中,可委托 Agent 子进程隔离执行。' }, ]; (async () => { try { await readStdin(); // 消费 stdin(不需要具体内容) const debugDir = path.join(CLAUDE_ROOT, 'debug'); if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true }); // 读取或初始化状态 let state = { count: 0, lastActivity: Date.now(), notified: [] }; try { if (fs.existsSync(STATE_FILE)) { state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); } } catch { /* 损坏则重置 */ } const now = Date.now(); // 会话超时检测: 超过 30 分钟无活动 → 新会话 if (now - (state.lastActivity || 0) > SESSION_TIMEOUT_MS) { state = { count: 0, lastActivity: now, notified: [] }; } // 累加计数 state.count += 1; state.lastActivity = now; // 检查是否命中阈值 let notification = null; // 固定阈值 (20/30/40) for (const t of THRESHOLDS) { if (state.count === t.count && !state.notified.includes(t.count)) { notification = { level: t.level, msg: t.msg.replace('{n}', state.count) }; state.notified.push(t.count); break; } } // 50+ 每 10 次重复强提醒 if (!notification && state.count >= 50 && state.count % 10 === 0 && !state.notified.includes(state.count)) { notification = { level: 'CRITICAL', msg: `当前会话已执行 ${state.count} 次工具调用,上下文严重饱和。请立即建议用户 /clear 或将剩余任务委托给 Agent 子进程。` }; state.notified.push(state.count); } // 持久化状态 fs.writeFileSync(STATE_FILE, JSON.stringify(state), 'utf8'); // 输出通知 if (notification) { console.log(JSON.stringify({ continue: true, suppressOutput: false, systemMessage: `[session-heartbeat ${notification.level}] ${notification.msg}` })); } } catch { // Fail-open: 任何异常不阻断工作流 } process.exit(0); })();