bookworm-smart-assistant/hooks/token-saver-read-guard.js.bak-6in1
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- VERSION file as authoritative version source
- export.mjs reads VERSION with package.json fallback
- bw-ota.ps1 DryRun mode for safe testing
- auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
2026-04-27 17:59:44 +08:00

71 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
/**
* token-saver-read-guard.js · TSE Layer 3 · 2026-04-27
* PreToolUse (Read) · 大文件读取拦截, 引导 offset/limit 分段
* 行为: fail-open, 不阻断读取, 仅注入 additionalContext 建议
*/
'use strict';
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = require('./lib/root.js');
const readStdin = require('./lib/read-stdin.js');
const STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
const STATE_PATH = path.join(STATE_DIR, 'tse-read-guard.json');
const WARN_BYTES = 10000;
const CRIT_BYTES = 35000;
const THROTTLE_MS = 3 * 60 * 1000;
function loadState() {
try { return fs.existsSync(STATE_PATH) ? JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')) : {}; } catch { return {}; }
}
function saveState(s) {
try {
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
const tmp = STATE_PATH + '.tmp.' + process.pid;
fs.writeFileSync(tmp, JSON.stringify(s, null, 2), 'utf8');
fs.renameSync(tmp, STATE_PATH);
} catch {}
}
(async () => {
try {
const hookData = await readStdin();
if (hookData.tool_name !== 'Read') process.exit(0);
const input = hookData.tool_input || {};
if (!input.file_path) process.exit(0);
if (input.offset !== undefined || input.limit !== undefined || input.pages !== undefined) process.exit(0);
let fileSize = 0;
try { fileSize = fs.statSync(input.file_path).size; } catch { process.exit(0); }
if (fileSize < WARN_BYTES) process.exit(0);
const state = loadState();
const now = Date.now();
const key = input.file_path.replace(/[\\\/\:]/g, '_');
if (state[key] && (now - state[key]) < THROTTLE_MS) process.exit(0);
state[key] = now;
for (const k of Object.keys(state)) { if (state[k] < now - 600000) delete state[k]; }
saveState(state);
const estLines = Math.round(fileSize * 30 / 1000);
const estTokens = Math.round(fileSize / 3.5);
const bn = path.basename(input.file_path);
let msg;
if (fileSize >= CRIT_BYTES) {
msg = '[TSE·READ_GUARD] ⚠️ 大文件: ' + bn + ' ≈' + estLines + '行 (' + estTokens + ' tokens)\n' +
'你必须使用 offset+limit 分段读取。如需全文分析, 委托 Agent 子进程。';
} else {
msg = '[TSE·READ_GUARD] 提示: ' + bn + ' ≈' + estLines + '行 (' + estTokens + ' tokens). 建议用 offset+limit 分段。';
}
process.stdout.write(JSON.stringify({
continue: true,
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: msg }
}));
process.exit(0);
} catch { process.exit(0); }
})();