bookworm-smart-assistant/hooks/session-start-restore.js

106 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* UserPromptSubmit Hook - 会话启动恢复器
* 1. 检测 handoff.json 存在时注入恢复上下文并归档
* 2. 每次会话首次 prompt 时重置 heartbeat 计数器(解决 /clear 后计数不归零问题)
*/
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = require('./lib/root.js');
const readStdin = require('./lib/read-stdin.js');
const SESSION_STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
const HANDOFF_PATH = path.join(SESSION_STATE_DIR, 'handoff.json');
const RESTORE_MARKER = path.join(SESSION_STATE_DIR, '.session-restored');
const HEARTBEAT_FILE = path.join(CLAUDE_ROOT, 'debug', 'session-heartbeat.json');
(async () => {
try {
let hookData = {};
try { hookData = await readStdin(); } catch (_) {}
const sessionId = hookData.session_id || '';
let messages = [];
// === 1. 会话 ID 变化检测 → 重置 heartbeat ===
let lastSessionId = '';
try {
if (fs.existsSync(RESTORE_MARKER)) {
lastSessionId = fs.readFileSync(RESTORE_MARKER, 'utf8').trim();
}
} catch (_) {}
if (sessionId && sessionId !== lastSessionId) {
// 新会话(/clear 或新窗口),重置 heartbeat 计数器
if (fs.existsSync(HEARTBEAT_FILE)) {
fs.writeFileSync(HEARTBEAT_FILE, JSON.stringify({
count: 0, lastActivity: Date.now(), notified: []
}), 'utf8');
}
// 记录当前 session_id
if (!fs.existsSync(SESSION_STATE_DIR)) {
fs.mkdirSync(SESSION_STATE_DIR, { recursive: true });
}
fs.writeFileSync(RESTORE_MARKER, sessionId, 'utf8');
// === 1b. Auto Git Pull新会话拉取最新代码===
try {
const sync = require('../scripts/auto-git-sync.js');
const pullMsgs = sync.pullLatest();
if (pullMsgs && pullMsgs.length > 0) {
messages.push(pullMsgs.join('\n'));
}
} catch (_) {} // fail-open
}
// === 2. Handoff 恢复 ===
if (fs.existsSync(HANDOFF_PATH)) {
let handoff = {};
try {
handoff = JSON.parse(fs.readFileSync(HANDOFF_PATH, 'utf8'));
} catch (_) {
fs.unlinkSync(HANDOFF_PATH);
console.log(JSON.stringify({ continue: true, suppressOutput: true }));
return process.exit(0);
}
// 检查是否过期24 小时)
const age = Date.now() - new Date(handoff.timestamp || 0).getTime();
if (age > 24 * 60 * 60 * 1000) {
const archiveName = `handoff-expired-${Date.now()}.json`;
fs.renameSync(HANDOFF_PATH, path.join(SESSION_STATE_DIR, archiveName));
console.log(JSON.stringify({ continue: true, suppressOutput: true }));
return process.exit(0);
}
// 归档并注入恢复上下文
const archiveName = `handoff-${Date.now()}.json`;
fs.renameSync(HANDOFF_PATH, path.join(SESSION_STATE_DIR, archiveName));
messages.push([
'[SESSION_RESTORE] 检测到上次会话的 handoff 记录:',
`- 时间: ${handoff.timestamp}`,
`- 工作目录: ${handoff.working_directory || 'unknown'}`,
`- 工具调用数: ${handoff.tool_call_count || 'unknown'}`,
`- 摘要: ${handoff.conversation_summary || '无'}`,
'',
'请检查 memory 和 task 列表以恢复上下文。'
].join('\n'));
}
if (messages.length > 0) {
console.log(JSON.stringify({
continue: true,
suppressOutput: false,
systemMessage: messages.join('\n')
}));
} else {
console.log(JSON.stringify({ continue: true, suppressOutput: true }));
}
} catch {
console.log(JSON.stringify({ continue: true, suppressOutput: true }));
}
process.exit(0);
})();